├── filters.gif ├── screenshot_1.png ├── screenshot_2.png ├── animated_filters.gif ├── Examples ├── ObjC │ └── Sources │ │ ├── en.lproj │ │ └── InfoPlist.strings │ │ ├── Default.png │ │ ├── a_filter.cisf │ │ ├── Default@2x.png │ │ ├── Default-568h@2x.png │ │ ├── Pause-Normal-icon.png │ │ ├── Play-Normal-icon.png │ │ ├── Play-Pressed-icon.png │ │ ├── capture_flip@2x.png │ │ ├── capture_onion@2x.png │ │ ├── Record-Normal-icon.png │ │ ├── Record-Pressed-icon.png │ │ ├── Stop-Normal-Red-icon.png │ │ ├── capture_onion_selected@2x.png │ │ ├── SCTouchDetector.h │ │ ├── SCAppDelegate.h │ │ ├── SCWatermarkOverlayView.h │ │ ├── main.m │ │ ├── SCRecorderExamples-Prefix.pch │ │ ├── SCSessionListViewController.h │ │ ├── SCImageDisplayerViewController.h │ │ ├── SCEditVideoViewController.h │ │ ├── SCSessionTableViewCell.h │ │ ├── Images.xcassets │ │ ├── LaunchImage.launchimage │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── SCRecordSessionManager.h │ │ ├── SCSessionTableViewCell.m │ │ ├── SCVideoPlayerViewController.h │ │ ├── SCTouchDetector.m │ │ ├── SCAudioRecordViewController.h │ │ ├── SCRecorderViewController.h │ │ ├── SCRecorderExamples-Info.plist │ │ ├── SCWatermarkOverlayView.m │ │ ├── SCAppDelegate.m │ │ ├── SCImageDisplayerViewController.m │ │ ├── SCRecordSessionManager.m │ │ ├── Launch Screen.storyboard │ │ ├── SCEditVideoViewController.m │ │ ├── SCSessionListViewController.m │ │ └── SCAudioRecordViewController.m └── Swift │ └── Sources │ └── RecorderViewController.swift ├── Library ├── SCRecorderMac │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── SCRecorderMac-Prefix.pch │ ├── SCRecorderMac.h │ └── SCRecorderMac-Info.plist ├── SCRecorderFramework │ ├── SCRecorder.modulemap │ ├── SCRecorderFramework.h │ └── Info.plist ├── Sources │ ├── UIImage+SCSaveToCameraRoll.h │ ├── SCAudioVideoRecorder-Prefix.pch │ ├── NSURL+SCSaveToCameraRoll.h │ ├── SCFilterImageView.h │ ├── SCProcessingQueue.h │ ├── SCSaveToCameraRollOperation.h │ ├── SCSampleBufferHolder.h │ ├── SCRecorderFocusTargetView.h │ ├── SCWeakSelectorTarget.h │ ├── UIImage+SCSaveToCameraRoll.m │ ├── SCFilter+VideoComposition.h │ ├── NSURL+SCSaveToCameraRoll.m │ ├── SCFilter+UIImage.h │ ├── SCIOPixelBuffers.h │ ├── SCFilterImageView.m │ ├── SCAudioTools.h │ ├── SCPhotoConfiguration.h │ ├── SCRecorderHeader.h │ ├── SCPhotoConfiguration.m │ ├── SCFilterAnimation.h │ ├── SCMediaTypeConfiguration.m │ ├── SCSampleBufferHolder.m │ ├── SCWeakSelectorTarget.m │ ├── SCFilter+VideoComposition.m │ ├── SCIOPixelBuffers.m │ ├── SCAudioConfiguration.h │ ├── SCFilter+UIImage.m │ ├── SCMediaTypeConfiguration.h │ ├── SCRecorderTools.h │ ├── SCVideoPlayerView.h │ ├── SCSwipeableFilterView.h │ ├── SCRecordSessionSegment.h │ ├── SCSaveToCameraRollOperation.m │ ├── SCAudioConfiguration.m │ ├── SCFilterAnimation.m │ ├── SCContext.h │ ├── SCRecordSession_Internal.h │ ├── SCRecorderToolsView.h │ ├── SCImageView.h │ ├── SCAssetExportSession.h │ ├── SCVideoPlayerView.m │ ├── SCAudioTools.m │ ├── SCProcessingQueue.m │ ├── SCVideoConfiguration.m │ ├── SCRecorderDelegate.h │ ├── SCRecordSessionSegment.m │ ├── SCPlayer.h │ ├── SCContext.m │ ├── SCVideoConfiguration.h │ ├── SCRecorderFocusTargetView.m │ ├── SCFilter.h │ ├── SCRecorderTools.m │ ├── SCSwipeableFilterView.m │ ├── SCRecordSession.h │ └── SCRecorderToolsView.m └── SCRecorderFrameworkTests │ ├── Info.plist │ └── SCRecorderFrameworkTests.m ├── XamarinBindings ├── SCRecorderBindings │ ├── AssemblyInfo.cs │ └── Extras.cs └── Makefile ├── .gitignore ├── SCRecorder.podspec └── LICENSE /filters.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/filters.gif -------------------------------------------------------------------------------- /screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/screenshot_1.png -------------------------------------------------------------------------------- /screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/screenshot_2.png -------------------------------------------------------------------------------- /animated_filters.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/animated_filters.gif -------------------------------------------------------------------------------- /Examples/ObjC/Sources/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Library/SCRecorderMac/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Default.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/a_filter.cisf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/a_filter.cisf -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Default@2x.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Default-568h@2x.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Pause-Normal-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Pause-Normal-icon.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Play-Normal-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Play-Normal-icon.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Play-Pressed-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Play-Pressed-icon.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/capture_flip@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/capture_flip@2x.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/capture_onion@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/capture_onion@2x.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Record-Normal-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Record-Normal-icon.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Record-Pressed-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Record-Pressed-icon.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Stop-Normal-Red-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/Stop-Normal-Red-icon.png -------------------------------------------------------------------------------- /Examples/ObjC/Sources/capture_onion_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rFlex/SCRecorder/HEAD/Examples/ObjC/Sources/capture_onion_selected@2x.png -------------------------------------------------------------------------------- /Library/SCRecorderFramework/SCRecorder.modulemap: -------------------------------------------------------------------------------- 1 | framework module SCRecorder { 2 | umbrella header "SCRecorderFramework.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /XamarinBindings/SCRecorderBindings/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MonoTouch.ObjCRuntime; 3 | 4 | [assembly: LinkWith ("libSCRecorder-Universal.a", LinkTarget.Simulator | LinkTarget.ArmV7, ForceLoad = true)] 5 | -------------------------------------------------------------------------------- /Library/SCRecorderMac/SCRecorderMac-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /Library/SCRecorderMac/SCRecorderMac.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderMac.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 20/05/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCFilter.h" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | *.a 20 | *~ 21 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCTouchDetector.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCTouchDetector.h 3 | // SCVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/6/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCTouchDetector : UIGestureRecognizer { 12 | 13 | } 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // VRAppDelegate.h 3 | // VideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/3/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCWatermarkOverlayView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCWatermarkOverlayView.h 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 16/06/15. 6 | // 7 | // 8 | 9 | #import 10 | #import "SCVideoConfiguration.h" 11 | 12 | @interface SCWatermarkOverlayView : UIView 13 | 14 | @property (strong, nonatomic) NSDate *date; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 14/04/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | #import "SCAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([SCAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Library/Sources/UIImage+SCSaveToCameraRoll.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+SCSaveToCameraRoll.h 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/12/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (SCSaveToCameraRoll) 12 | 13 | - (void)saveToCameraRollWithCompletion:(void (^__nullable)(NSError * _Nullable error))completion; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Library/Sources/SCAudioVideoRecorder-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'VideoRecorder' target in the 'VideoRecorder' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Library/Sources/NSURL+SCSaveToCameraRoll.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+SCSaveToCameraRoll.h 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/10/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSURL (SCSaveToCameraRoll) 12 | 13 | - (void)saveToCameraRollWithCompletion:(void (^__nullable)(NSString * _Nullable path, NSError * _Nullable error))completion; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCRecorderExamples-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Library/Sources/SCFilterImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilterImageView.h 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/8/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCImageView.h" 10 | #import "SCFilter.h" 11 | 12 | @interface SCFilterImageView : SCImageView 13 | 14 | /** 15 | The filter to apply when rendering. If nil is set, no filter will be applied 16 | */ 17 | @property (strong, nonatomic) SCFilter *__nullable filter; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCSessionListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCSessionListViewController.h 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 14/08/14. 6 | // 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCSessionListViewController : UIViewController 13 | 14 | @property (strong, nonatomic) SCRecorder *recorder; 15 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Library/Sources/SCProcessingQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCProcessingQueue.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 02/07/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCProcessingQueue : NSObject 12 | 13 | @property (assign, nonatomic) NSUInteger maxQueueSize; 14 | 15 | - (void)startProcessingWithBlock:(id(^)())processingBlock; 16 | 17 | - (void)stopProcessing; 18 | 19 | - (id)dequeue; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCImageDisplayerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCImageViewDisPlayViewController.h 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by 曾 宪华 on 13-11-5. 6 | // Copyright (c) 2013年 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCImageDisplayerViewController : UIViewController 13 | 14 | @property (nonatomic, strong) UIImage *photo; 15 | @property (weak, nonatomic) IBOutlet SCSwipeableFilterView *filterSwitcherView; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Library/Sources/SCSaveToCameraRollOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCSaveToCameraRollOperation.h 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/12/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCSaveToCameraRollOperation : NSObject 12 | 13 | - (void)saveVideoURL:(NSURL *)url completion:(void(^)(NSString *, NSError *))completion; 14 | 15 | - (void)saveImage:(UIImage *)image completion:(void(^)(NSError *))completion; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Library/Sources/SCSampleBufferHolder.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCSampleBufferHolder.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 10/09/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SCSampleBufferHolder : NSObject 13 | 14 | @property (assign, nonatomic) CMSampleBufferRef sampleBuffer; 15 | 16 | + (SCSampleBufferHolder *)sampleBufferHolderWithSampleBuffer:(CMSampleBufferRef)sampleBuffer; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCEditVideoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCEditVideoViewController.h 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 22/07/14. 6 | // 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCEditVideoViewController : UIViewController 13 | 14 | @property (strong, nonatomic) SCRecordSession *recordSession; 15 | @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; 16 | - (IBAction)deletePressed:(id)sender; 17 | @property (weak, nonatomic) IBOutlet SCVideoPlayerView *videoPlayerView; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCSessionTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCSessionTableViewCell.h 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 14/08/14. 6 | // 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCSessionTableViewCell : UITableViewCell 13 | @property (weak, nonatomic) IBOutlet SCVideoPlayerView *videoPlayerView; 14 | @property (weak, nonatomic) IBOutlet UILabel *dateLabel; 15 | @property (weak, nonatomic) IBOutlet UILabel *segmentsCountLabel; 16 | @property (weak, nonatomic) IBOutlet UILabel *durationLabel; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderFocusTargetView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XHCameraTagetView.h 3 | // iyilunba 4 | // 5 | // Created by 曾 宪华 on 13-11-8. 6 | // Copyright (c) 2013年 曾 宪华 开发团队(http://iyilunba.com ). All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCRecorderFocusTargetView : UIView 12 | 13 | @property (strong, nonatomic) UIImage *outsideFocusTargetImage; 14 | @property (strong, nonatomic) UIImage *insideFocusTargetImage; 15 | @property (assign, nonatomic) float insideFocusTargetImageSizeRatio; 16 | 17 | - (void)startTargeting; 18 | - (void)stopTargeting; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /XamarinBindings/SCRecorderBindings/Extras.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Drawing; 4 | using System.Runtime.InteropServices; 5 | using MonoTouch; 6 | using MonoTouch.CoreFoundation; 7 | using MonoTouch.CoreMedia; 8 | using MonoTouch.CoreMotion; 9 | using MonoTouch.Foundation; 10 | using MonoTouch.ObjCRuntime; 11 | using MonoTouch.CoreAnimation; 12 | using MonoTouch.CoreLocation; 13 | using MonoTouch.MapKit; 14 | using MonoTouch.UIKit; 15 | using MonoTouch.CoreGraphics; 16 | using MonoTouch.NewsstandKit; 17 | using MonoTouch.GLKit; 18 | using OpenTK; 19 | 20 | namespace SCAudioVideoRecorder { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Library/Sources/SCWeakSelectorTarget.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCWeakSelectorTarget.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 04/04/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCWeakSelectorTarget : NSObject 12 | 13 | @property (readonly, nonatomic, weak) id target; 14 | @property (readonly, nonatomic) SEL targetSelector; 15 | @property (readonly, nonatomic) SEL handleSelector; 16 | 17 | - (instancetype)initWithTarget:(id)target targetSelector:(SEL)targetSelector; 18 | 19 | - (BOOL)sendMessageToTarget:(id)param; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Library/Sources/UIImage+SCSaveToCameraRoll.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+SCSaveToCameraRoll.m 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/12/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "UIImage+SCSaveToCameraRoll.h" 10 | #import "SCSaveToCameraRollOperation.h" 11 | 12 | @implementation UIImage (SCSaveToCameraRoll) 13 | 14 | - (void)saveToCameraRollWithCompletion:(void (^)(NSError * _Nullable))completion { 15 | SCSaveToCameraRollOperation *saveToCameraRoll = [SCSaveToCameraRollOperation new]; 16 | [saveToCameraRoll saveImage:self completion:completion]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Library/Sources/SCFilter+VideoComposition.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilter+VideoComposition.h 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/17/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCFilter.h" 11 | 12 | @interface SCFilter (VideoComposition) 13 | 14 | /** 15 | Creates and returns a videoComposition that will process the given asset with this filter. 16 | Returns nil on unsupported platforms. 17 | */ 18 | - (AVMutableVideoComposition *__nullable)videoCompositionWithAsset:(AVAsset *__nonnull)asset NS_AVAILABLE(10_11, 9_0); 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCRecordSessionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecordSessionManager.h 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 15/08/14. 6 | // 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCRecordSessionManager : NSObject 13 | 14 | - (void)saveRecordSession:(SCRecordSession *)recordSession; 15 | 16 | - (void)removeRecordSession:(SCRecordSession *)recordSession; 17 | 18 | - (BOOL)isSaved:(SCRecordSession *)recordSession; 19 | 20 | - (void)removeRecordSessionAtIndex:(NSInteger)index; 21 | 22 | - (NSArray *)savedRecordSessions; 23 | 24 | + (SCRecordSessionManager *)sharedInstance; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Library/Sources/NSURL+SCSaveToCameraRoll.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+SCSaveToCameraRoll.m 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/10/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NSURL+SCSaveToCameraRoll.h" 11 | #import "SCSaveToCameraRollOperation.h" 12 | 13 | @implementation NSURL (SCSaveToCameraRoll) 14 | 15 | - (void)saveToCameraRollWithCompletion:(void (^)(NSString * _Nullable path, NSError * _Nullable error))completion { 16 | SCSaveToCameraRollOperation *saveToCameraRoll = [SCSaveToCameraRollOperation new]; 17 | [saveToCameraRoll saveVideoURL:self completion:completion]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Library/Sources/SCFilter+UIImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilter+UIImage.h 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/18/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCFilter.h" 11 | 12 | @interface SCFilter (UIImage) 13 | 14 | /** 15 | Returns a UIImage by processing this filter into the given UIImage 16 | */ 17 | - (UIImage *__nullable)UIImageByProcessingUIImage:(UIImage *__nullable)image atTime:(CFTimeInterval)time; 18 | 19 | /** 20 | Returns a UIImage by processing this filter into the given UIImage 21 | */ 22 | - (UIImage *__nullable)UIImageByProcessingUIImage:(UIImage *__nullable)image; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Library/SCRecorderFramework/SCRecorderFramework.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderFramework.h 3 | // SCRecorderFramework 4 | // 5 | // Created by Simon CORSIN on 28/03/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SCRecorderFramework. 12 | FOUNDATION_EXPORT double SCRecorderFrameworkVersionNumber; 13 | 14 | //! Project version string for SCRecorderFramework. 15 | FOUNDATION_EXPORT const unsigned char SCRecorderFrameworkVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | #import -------------------------------------------------------------------------------- /Library/Sources/SCIOPixelBuffers.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoBuffer.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 02/07/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SCIOPixelBuffers : NSObject 13 | 14 | @property (readonly, nonatomic) CMTime time; 15 | 16 | @property (readonly, nonatomic) CVPixelBufferRef inputPixelBuffer; 17 | 18 | @property (readonly, nonatomic) CVPixelBufferRef outputPixelBuffer; 19 | 20 | + (SCIOPixelBuffers *)IOPixelBuffersWithInputPixelBuffer:(CVPixelBufferRef)inputPixelBuffer outputPixelBuffer:(CVPixelBufferRef)outputPixelBuffer time:(CMTime)time; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Library/Sources/SCFilterImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilterImageView.m 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/8/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCFilterImageView.h" 10 | 11 | @implementation SCFilterImageView 12 | 13 | - (CIImage *)renderedCIImageInRect:(CGRect)rect { 14 | CIImage *image = [super renderedCIImageInRect:rect]; 15 | 16 | if (image != nil) { 17 | if (_filter != nil) { 18 | image = [_filter imageByProcessingImage:image atTime:self.CIImageTime]; 19 | } 20 | } 21 | 22 | return image; 23 | } 24 | 25 | - (void)setFilter:(SCFilter *)filter { 26 | _filter = filter; 27 | 28 | [self setNeedsDisplay]; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Library/Sources/SCAudioTools.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCAudioTools.h 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/8/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCAudioTools : NSObject { 12 | 13 | } 14 | 15 | // 16 | // IOS SPECIFIC 17 | // 18 | 19 | #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE 20 | + (void)overrideCategoryMixWithOthers; 21 | #endif 22 | + (void)mixAudio:(AVAsset*)audioAsset startTime:(CMTime)startTime withVideo:(NSURL*)inputUrl affineTransform:(CGAffineTransform)affineTransform toUrl:(NSURL*)outputUrl outputFileType:(NSString*)outputFileType withMaxDuration:(CMTime)maxDuration withCompletionBlock:(void(^)(NSError *))completionBlock; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCSessionTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCSessionTableViewCell.m 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 14/08/14. 6 | // 7 | // 8 | 9 | #import "SCSessionTableViewCell.h" 10 | 11 | @implementation SCSessionTableViewCell 12 | 13 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 14 | { 15 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 16 | if (self) { 17 | // Initialization code 18 | } 19 | return self; 20 | } 21 | 22 | - (void)awakeFromNib 23 | { 24 | // Initialization code 25 | } 26 | 27 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 28 | { 29 | [super setSelected:selected animated:animated]; 30 | 31 | // Configure the view for the selected state 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCVideoPlayerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoPlayerViewController.h 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/30/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCVideoPlayerViewController : UIViewController 13 | 14 | @property (strong, nonatomic) SCRecordSession *recordSession; 15 | @property (weak, nonatomic) IBOutlet SCSwipeableFilterView *filterSwitcherView; 16 | @property (weak, nonatomic) IBOutlet UILabel *filterNameLabel; 17 | @property (weak, nonatomic) IBOutlet UIView *exportView; 18 | @property (weak, nonatomic) IBOutlet UIView *progressView; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Library/Sources/SCPhotoConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCPhotoConfiguration.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 24/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCMediaTypeConfiguration.h" 10 | 11 | @interface SCPhotoConfiguration : NSObject 12 | 13 | /** 14 | Whether the photo output is enabled or not. 15 | Changing this value after the session has been opened 16 | on the SCRecorder has no effect. 17 | */ 18 | @property (assign, nonatomic) BOOL enabled; 19 | 20 | /** 21 | If set, every other properties but "enabled" will be ignored 22 | and this options dictionary will be used instead. 23 | */ 24 | @property (copy, nonatomic) NSDictionary *__nullable options; 25 | 26 | /** 27 | Returns the output settings for the 28 | */ 29 | - (NSDictionary *__nonnull)createOutputSettings; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderHeader.h 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/12/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCRecorder.h" 10 | #import "SCRecordSession.h" 11 | #import "SCSampleBufferHolder.h" 12 | #import "SCVideoPlayerView.h" 13 | #import "SCPlayer.h" 14 | #import "SCAssetExportSession.h" 15 | #import "SCImageView.h" 16 | #import "SCSwipeableFilterView.h" 17 | #import "SCRecorderToolsView.h" 18 | #import "SCVideoConfiguration.h" 19 | #import "SCAudioConfiguration.h" 20 | #import "SCPhotoConfiguration.h" 21 | #import "SCRecorderTools.h" 22 | #import "SCRecorderDelegate.h" 23 | #import "SCContext.h" 24 | #import "NSURL+SCSaveToCameraRoll.h" 25 | #import "UIImage+SCSaveToCameraRoll.h" 26 | #import "SCFilter+VideoComposition.h" 27 | #import "SCFilter+UIImage.h" 28 | -------------------------------------------------------------------------------- /Library/SCRecorderFrameworkTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | me.corsin.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Library/Sources/SCPhotoConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCPhotoConfiguration.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 24/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCPhotoConfiguration.h" 10 | 11 | @implementation SCPhotoConfiguration 12 | 13 | - (id)init { 14 | self = [super init]; 15 | 16 | if (self) { 17 | _enabled = YES; 18 | } 19 | 20 | return self; 21 | } 22 | 23 | - (void)setOptions:(NSDictionary *)options { 24 | [self willChangeValueForKey:@"options"]; 25 | 26 | _options = options; 27 | 28 | [self didChangeValueForKey:@"options"]; 29 | } 30 | 31 | - (NSDictionary *)createOutputSettings { 32 | if (_options == nil) { 33 | return @{AVVideoCodecKey : AVVideoCodecJPEG}; 34 | } else { 35 | return _options; 36 | } 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCTouchDetector.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCTouchDetector.m 3 | // SCVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/6/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCTouchDetector.h" 10 | #import 11 | 12 | @implementation SCTouchDetector 13 | 14 | - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 15 | if (self.enabled) { 16 | self.state = UIGestureRecognizerStateBegan; 17 | } 18 | } 19 | 20 | - (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { 21 | if (self.enabled) { 22 | self.state = UIGestureRecognizerStateEnded; 23 | } 24 | } 25 | 26 | - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 27 | if (self.enabled) { 28 | self.state = UIGestureRecognizerStateEnded; 29 | } 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /SCRecorder.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "SCRecorder" 4 | s.version = "2.7.0" 5 | s.summary = "The camera engine that is complete, for real." 6 | 7 | s.description = <<-DESC 8 | Complete iOS camera engine with Vine-like tap to record, animated filters, slow motion, segments editing 9 | DESC 10 | 11 | s.homepage = "https://github.com/rFlex/SCRecorder" 12 | s.license = 'Apache License, Version 2.0' 13 | s.author = { "Simon CORSIN" => "simon@corsin.me" } 14 | s.platform = :ios, '7.0' 15 | s.source = { :git => "https://github.com/rFlex/SCRecorder.git", :tag => "v2.7.0" } 16 | s.source_files = 'Library/Sources/*.{h,m}' 17 | s.public_header_files = 'Library/Sources/*.h' 18 | s.requires_arc = true 19 | s.weak_frameworks = 'Metal', 'GLKit' 20 | s.frameworks = 'AVFoundation' 21 | 22 | end 23 | -------------------------------------------------------------------------------- /Library/SCRecorderFramework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | me.corsin.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Library/Sources/SCFilterAnimation.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilterAnimation.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 06/05/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SCFilterAnimation : NSObject 12 | 13 | @property (readonly, nonatomic) NSString *__nonnull key; 14 | 15 | @property (readonly, nonatomic) __nullable id startValue; 16 | 17 | @property (readonly, nonatomic) __nullable id endValue; 18 | 19 | @property (readonly, nonatomic) CFTimeInterval startTime; 20 | 21 | @property (readonly, nonatomic) CFTimeInterval duration; 22 | 23 | - (__nullable id)valueAtTime:(CFTimeInterval)time; 24 | 25 | - (BOOL)hasValueAtTime:(CFTimeInterval)time; 26 | 27 | + (SCFilterAnimation *__nonnull)filterAnimationForParameterKey:(NSString *__nonnull)key startValue:(__nullable id)startValue endValue:(__nullable id)endValue startTime:(CFTimeInterval)startTime duration:(CFTimeInterval)duration; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Library/Sources/SCMediaTypeConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCConfiguration.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 21/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCMediaTypeConfiguration.h" 10 | 11 | @implementation SCMediaTypeConfiguration 12 | 13 | const NSString *SCPresetHighestQuality = @"HighestQuality"; 14 | const NSString *SCPresetMediumQuality = @"MediumQuality"; 15 | const NSString *SCPresetLowQuality = @"LowQuality"; 16 | 17 | - (id)init { 18 | self = [super init]; 19 | 20 | if (self) { 21 | _enabled = YES; 22 | } 23 | 24 | return self; 25 | } 26 | 27 | - (NSDictionary *)createAssetWriterOptionsUsingSampleBuffer:(CMSampleBufferRef)sampleBuffer { 28 | return nil; 29 | } 30 | 31 | - (void)setEnabled:(BOOL)enabled { 32 | if (_enabled != enabled) { 33 | [self willChangeValueForKey:@"enabled"]; 34 | _enabled = enabled; 35 | [self didChangeValueForKey:@"enabled"]; 36 | } 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Library/Sources/SCSampleBufferHolder.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCSampleBufferHolder.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 10/09/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCSampleBufferHolder.h" 10 | 11 | @implementation SCSampleBufferHolder 12 | 13 | - (void)dealloc { 14 | if (_sampleBuffer != nil) { 15 | CFRelease(_sampleBuffer); 16 | } 17 | } 18 | 19 | - (void)setSampleBuffer:(CMSampleBufferRef)sampleBuffer { 20 | if (_sampleBuffer != nil) { 21 | CFRelease(_sampleBuffer); 22 | _sampleBuffer = nil; 23 | } 24 | 25 | _sampleBuffer = sampleBuffer; 26 | 27 | if (sampleBuffer != nil) { 28 | CFRetain(sampleBuffer); 29 | } 30 | } 31 | 32 | + (SCSampleBufferHolder *)sampleBufferHolderWithSampleBuffer:(CMSampleBufferRef)sampleBuffer { 33 | SCSampleBufferHolder *sampleBufferHolder = [SCSampleBufferHolder new]; 34 | 35 | sampleBufferHolder.sampleBuffer = sampleBuffer; 36 | 37 | return sampleBufferHolder; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Library/Sources/SCWeakSelectorTarget.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCWeakSelectorTarget.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 04/04/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCWeakSelectorTarget.h" 10 | 11 | @implementation SCWeakSelectorTarget 12 | 13 | - (instancetype)initWithTarget:(id)target targetSelector:(SEL)sel { 14 | self = [super init]; 15 | 16 | if (self) { 17 | _target = target; 18 | _targetSelector = sel; 19 | } 20 | 21 | return self; 22 | } 23 | 24 | - (BOOL)sendMessageToTarget:(id)param { 25 | id strongTarget = _target; 26 | 27 | if (strongTarget != nil) { 28 | #pragma clang diagnostic push 29 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 30 | [strongTarget performSelector:_targetSelector withObject:param]; 31 | #pragma clang diagnostic pop 32 | 33 | return YES; 34 | } 35 | 36 | return NO; 37 | } 38 | 39 | - (SEL)handleSelector { 40 | return @selector(sendMessageToTarget:); 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Library/SCRecorderMac/SCRecorderMac-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | me.corsin.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2014 rFlex. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Library/Sources/SCFilter+VideoComposition.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilter+VideoComposition.m 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/17/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCFilter+VideoComposition.h" 10 | 11 | @implementation SCFilter (VideoComposition) 12 | 13 | - (AVMutableVideoComposition *)videoCompositionWithAsset:(AVAsset *)asset { 14 | if ([[AVVideoComposition class] respondsToSelector:@selector(videoCompositionWithAsset:applyingCIFiltersWithHandler:)]) { 15 | CIContext *context = [CIContext contextWithOptions:@{kCIContextWorkingColorSpace : [NSNull null], kCIContextOutputColorSpace : [NSNull null]}]; 16 | return [AVMutableVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) { 17 | CIImage *image = [self imageByProcessingImage:request.sourceImage atTime:CMTimeGetSeconds(request.compositionTime)]; 18 | 19 | [request finishWithImage:image context:context]; 20 | }]; 21 | 22 | } 23 | return nil; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Library/SCRecorderFrameworkTests/SCRecorderFrameworkTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderFrameworkTests.m 3 | // SCRecorderFrameworkTests 4 | // 5 | // Created by Simon CORSIN on 28/03/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SCRecorderFrameworkTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation SCRecorderFrameworkTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCAudioRecordViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCAudioRecordViewController.h 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 18/12/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCAudioRecordViewController : UIViewController 13 | 14 | @property (weak, nonatomic) IBOutlet UIButton *stopRecordingButton; 15 | @property (weak, nonatomic) IBOutlet UIButton *recordButton; 16 | @property (weak, nonatomic) IBOutlet UILabel *recordTimeLabel; 17 | 18 | @property (weak, nonatomic) IBOutlet UIView *playView; 19 | @property (weak, nonatomic) IBOutlet UIButton *playButton; 20 | @property (weak, nonatomic) IBOutlet UISlider *playSlider; 21 | @property (weak, nonatomic) IBOutlet UIButton *deleteButton; 22 | @property (weak, nonatomic) IBOutlet UILabel *playLabel; 23 | 24 | - (IBAction)recordPressed:(id)sender; 25 | - (IBAction)stopRecordPressed:(id)sender; 26 | - (IBAction)playButtonPressed:(id)sender; 27 | - (IBAction)playSliderValueChanged:(id)sender; 28 | - (IBAction)deletePressed:(id)sender; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Library/Sources/SCIOPixelBuffers.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoBuffer.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 02/07/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCIOPixelBuffers.h" 10 | 11 | @implementation SCIOPixelBuffers 12 | 13 | - (instancetype)initWithInputPixelBuffer:(CVPixelBufferRef)inputPixelBuffer outputPixelBuffer:(CVPixelBufferRef)outputPixelBuffer time:(CMTime)time { 14 | self = [super init]; 15 | 16 | if (self) { 17 | _inputPixelBuffer = inputPixelBuffer; 18 | _outputPixelBuffer = outputPixelBuffer; 19 | _time = time; 20 | 21 | CVPixelBufferRetain(inputPixelBuffer); 22 | CVPixelBufferRetain(outputPixelBuffer); 23 | } 24 | 25 | return self; 26 | } 27 | 28 | - (void)dealloc { 29 | CVPixelBufferRelease(_inputPixelBuffer); 30 | CVPixelBufferRelease(_outputPixelBuffer); 31 | } 32 | 33 | + (SCIOPixelBuffers *)IOPixelBuffersWithInputPixelBuffer:(CVPixelBufferRef)inputPixelBuffer outputPixelBuffer:(CVPixelBufferRef)outputPixelBuffer time:(CMTime)time { 34 | return [[SCIOPixelBuffers alloc] initWithInputPixelBuffer:inputPixelBuffer outputPixelBuffer:outputPixelBuffer time:time]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Examples/Swift/Sources/RecorderViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecorderViewController.swift 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 28/03/15. 6 | // 7 | // 8 | 9 | import UIKit 10 | import SCRecorder 11 | 12 | class RecorderViewController: UIViewController { 13 | 14 | var recorder: SCRecorder! 15 | var photo: UIImage? 16 | var recordSession: SCRecordSession? 17 | 18 | @IBOutlet weak var bottomBar: UIView! 19 | @IBOutlet weak var loadingView: UIView! 20 | @IBOutlet weak var previewView: UIView! 21 | @IBAction func switchCameraMode(sender: AnyObject) { 22 | } 23 | 24 | @IBAction func switchFlashButton(sender: AnyObject) { 25 | } 26 | @IBOutlet weak var flashModeButton: UIButton! 27 | @IBAction func switchGhostMode(sender: AnyObject) { 28 | } 29 | @IBOutlet weak var ghostModeButton: UIButton! 30 | @IBOutlet weak var switchCameraModeButton: UIButton! 31 | @IBAction func reverseCamera(sender: AnyObject) { 32 | 33 | } 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | 38 | } 39 | 40 | override func preferredStatusBarStyle() -> UIStatusBarStyle { 41 | return UIStatusBarStyle.LightContent 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /XamarinBindings/Makefile: -------------------------------------------------------------------------------- 1 | # Directories 2 | NAME=SCRecorder 3 | TARGET=$(NAME) 4 | BINDDIR=$(NAME)Bindings 5 | PROJECT_ROOT=../Library 6 | PROJECT=$(PROJECT_ROOT)/$(NAME).xcodeproj 7 | 8 | # Binaries 9 | XBUILD=/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild 10 | BTOUCH=/Developer/MonoTouch/usr/bin/btouch 11 | 12 | 13 | SRC=AssemblyInfo.cs \ 14 | Bindings.cs \ 15 | Extras.cs 16 | 17 | all: $(NAME).dll 18 | 19 | lib$(NAME)-i386.a: 20 | $(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphonesimulator -configuration Release clean build 21 | -mv $(PROJECT_ROOT)/build/Release-iphonesimulator/lib$(TARGET).a $@ 22 | 23 | lib$(NAME)-armv7.a: 24 | $(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch armv7 -configuration Release clean build 25 | -mv $(PROJECT_ROOT)/build/Release-iphoneos/lib$(TARGET).a $@ 26 | 27 | lib$(NAME)-Universal.a: lib$(NAME)-armv7.a lib$(NAME)-i386.a 28 | lipo -create -output $@ $^ 29 | 30 | $(NAME).dll: $(BINDDIR)/AssemblyInfo.cs $(BINDDIR)/ApiDefinition.cs $(BINDDIR)/Extras.cs lib$(NAME)-Universal.a 31 | $(BTOUCH) -unsafe --outdir=tmp -out:$@ $(BINDDIR)/ApiDefinition.cs -x=$(BINDDIR)/AssemblyInfo.cs -x=$(BINDDIR)/Extras.cs --link-with=lib$(NAME)-Universal.a,lib$(NAME)-Universal.a 32 | 33 | clean: 34 | -rm -f *.a *.dll 35 | -rm -fr tmp $(PROJECT_ROOT)/build 36 | -------------------------------------------------------------------------------- /Library/Sources/SCAudioConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCAudioConfiguration.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 21/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCMediaTypeConfiguration.h" 11 | 12 | #define kSCAudioConfigurationDefaultBitrate 128000 13 | #define kSCAudioConfigurationDefaultNumberOfChannels 2 14 | #define kSCAudioConfigurationDefaultSampleRate 44100 15 | #define kSCAudioConfigurationDefaultAudioFormat kAudioFormatMPEG4AAC 16 | 17 | @interface SCAudioConfiguration : SCMediaTypeConfiguration 18 | 19 | /** 20 | Set the sample rate of the audio 21 | If set to 0, the original sample rate will be used. 22 | If options has been changed, this property will be ignored 23 | */ 24 | @property (assign, nonatomic) int sampleRate; 25 | 26 | /** 27 | Set the number of channels 28 | If set to 0, the original channels number will be used. 29 | If options is not nil, this property will be ignored 30 | */ 31 | @property (assign, nonatomic) int channelsCount; 32 | 33 | /** 34 | Must be like kAudioFormat* (example kAudioFormatMPEGLayer3) 35 | If options is not nil, this property will be ignored 36 | */ 37 | @property (assign, nonatomic) int format; 38 | 39 | /** 40 | The audioMix to apply. 41 | 42 | Only used in SCAssetExportSession. 43 | */ 44 | @property (strong, nonatomic) AVAudioMix *__nullable audioMix; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCRecorderViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VRViewController.h 3 | // VideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/3/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | @interface SCRecorderViewController : UIViewController 13 | 14 | @property (weak, nonatomic) IBOutlet UIView *recordView; 15 | @property (weak, nonatomic) IBOutlet UIButton *stopButton; 16 | @property (weak, nonatomic) IBOutlet UIButton *retakeButton; 17 | @property (weak, nonatomic) IBOutlet UIView *previewView; 18 | @property (weak, nonatomic) IBOutlet UIView *loadingView; 19 | @property (weak, nonatomic) IBOutlet UILabel *timeRecordedLabel; 20 | @property (weak, nonatomic) IBOutlet UIView *downBar; 21 | @property (weak, nonatomic) IBOutlet UIButton *switchCameraModeButton; 22 | @property (weak, nonatomic) IBOutlet UIButton *reverseCamera; 23 | @property (weak, nonatomic) IBOutlet UIButton *flashModeButton; 24 | @property (weak, nonatomic) IBOutlet UIButton *capturePhotoButton; 25 | @property (weak, nonatomic) IBOutlet UIButton *ghostModeButton; 26 | @property (weak, nonatomic) IBOutlet UIView *toolsContainerView; 27 | @property (weak, nonatomic) IBOutlet UIButton *openToolsButton; 28 | 29 | - (IBAction)switchCameraMode:(id)sender; 30 | - (IBAction)switchFlash:(id)sender; 31 | - (IBAction)capturePhoto:(id)sender; 32 | - (IBAction)switchGhostMode:(id)sender; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCRecorderExamples-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | me.corsin.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | Launch Screen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Library/Sources/SCFilter+UIImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilter+UIImage.m 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/18/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCContext.h" 10 | #import "SCFilter+UIImage.h" 11 | 12 | @implementation SCFilter (UIImage) 13 | 14 | - (UIImage *)UIImageByProcessingUIImage:(UIImage *)image { 15 | return [self UIImageByProcessingUIImage:image atTime:0]; 16 | } 17 | 18 | - (UIImage *)UIImageByProcessingUIImage:(UIImage *)uiImage atTime:(CFTimeInterval)time { 19 | static SCContext *context = nil; 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | context = [SCContext contextWithType:SCContextTypeDefault options:nil]; 23 | }); 24 | 25 | CIImage *image = nil; 26 | 27 | if (uiImage != nil) { 28 | if (uiImage.CIImage != nil) { 29 | image = uiImage.CIImage; 30 | } else { 31 | image = [CIImage imageWithCGImage:uiImage.CGImage]; 32 | } 33 | } 34 | 35 | image = [self imageByProcessingImage:image atTime:time]; 36 | 37 | if (image != nil) { 38 | CGImageRef cgImage = [context.CIContext createCGImage:image fromRect:image.extent]; 39 | 40 | UIImage *outputImage = nil; 41 | if (uiImage != nil) { 42 | outputImage = [UIImage imageWithCGImage:cgImage scale:uiImage.scale orientation:uiImage.imageOrientation]; 43 | } else { 44 | outputImage = [UIImage imageWithCGImage:cgImage]; 45 | } 46 | 47 | CGImageRelease(cgImage); 48 | 49 | return outputImage; 50 | } else { 51 | return nil; 52 | } 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Library/Sources/SCMediaTypeConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCConfiguration.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 21/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | extern NSString *__nonnull SCPresetHighestQuality; 13 | extern NSString *__nonnull SCPresetMediumQuality; 14 | extern NSString *__nonnull SCPresetLowQuality; 15 | 16 | @interface SCMediaTypeConfiguration : NSObject 17 | 18 | /** 19 | Whether this media type is enabled or not. 20 | */ 21 | @property (assign, nonatomic) BOOL enabled; 22 | 23 | /** 24 | Whether this input type should be ignored. Unlike the "enabled" property, 25 | this does not remove the input or outputs. It just asks the recorder to not 26 | write the buffers even though it is enabled. This is only needed if you want 27 | to quickly enable/disable this media type without reconfiguring all the input/outputs 28 | which can be is a quite slow operation to do. 29 | */ 30 | @property (assign, nonatomic) BOOL shouldIgnore; 31 | 32 | /** 33 | Set the bitrate of the audio 34 | If options is not nil,, this property will be ignored 35 | */ 36 | @property (assign, nonatomic) UInt64 bitrate; 37 | 38 | /** 39 | If set, every other properties but "enabled" will be ignored 40 | and this options dictionary will be used instead. 41 | */ 42 | @property (copy, nonatomic) NSDictionary *__nullable options; 43 | 44 | /** 45 | Defines a preset to use. If set, most properties will be 46 | ignored to use values that reflect this preset. 47 | */ 48 | @property (copy, nonatomic) NSString *__nullable preset; 49 | 50 | - (NSDictionary *__nonnull)createAssetWriterOptionsUsingSampleBuffer:(CMSampleBufferRef __nullable)sampleBuffer; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderTools.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderTools.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 24/12/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SCRecorderTools : NSObject 13 | 14 | /** 15 | Returns the best session preset that is compatible with all available video 16 | devices (front and back camera). It will ensure that buffer output from 17 | both camera has the same resolution. 18 | */ 19 | + (NSString *__nonnull)bestCaptureSessionPresetCompatibleWithAllDevices; 20 | 21 | /** 22 | Returns the best captureSessionPreset for a device that is equal or under the max specified size 23 | */ 24 | + (NSString *__nonnull)bestCaptureSessionPresetForDevice:(AVCaptureDevice *__nonnull)device withMaxSize:(CGSize)maxSize; 25 | 26 | /** 27 | Returns the best captureSessionPreset for a device position that is equal or under the max specified size 28 | */ 29 | + (NSString *__nonnull)bestCaptureSessionPresetForDevicePosition:(AVCaptureDevicePosition)devicePosition withMaxSize:(CGSize)maxSize; 30 | 31 | + (BOOL)formatInRange:(AVCaptureDeviceFormat *__nonnull)format frameRate:(CMTimeScale)frameRate; 32 | 33 | + (BOOL)formatInRange:(AVCaptureDeviceFormat *__nonnull)format frameRate:(CMTimeScale)frameRate dimensions:(CMVideoDimensions)videoDimensions; 34 | 35 | + (CMTimeScale)maxFrameRateForFormat:(AVCaptureDeviceFormat *__nonnull)format minFrameRate:(CMTimeScale)minFrameRate; 36 | 37 | + (AVCaptureDevice *__nonnull)videoDeviceForPosition:(AVCaptureDevicePosition)position; 38 | 39 | + (NSArray *__nonnull)assetWriterMetadata; 40 | 41 | @end 42 | 43 | @interface NSDate (SCRecorderTools) 44 | 45 | - (NSString *__nonnull)toISO8601; 46 | 47 | + (NSDate *__nullable)fromISO8601:(NSString *__nonnull)iso8601; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCWatermarkOverlayView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCWatermarkOverlayView.m 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 16/06/15. 6 | // 7 | // 8 | 9 | #import "SCWatermarkOverlayView.h" 10 | 11 | @interface SCWatermarkOverlayView() { 12 | UILabel *_watermarkLabel; 13 | UILabel *_timeLabel; 14 | } 15 | 16 | 17 | @end 18 | 19 | @implementation SCWatermarkOverlayView 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame { 22 | self = [super initWithFrame:frame]; 23 | 24 | if (self) { 25 | _watermarkLabel = [UILabel new]; 26 | _watermarkLabel.textColor = [UIColor whiteColor]; 27 | _watermarkLabel.font = [UIFont boldSystemFontOfSize:40]; 28 | _watermarkLabel.text = @"SCRecorder ©"; 29 | 30 | _timeLabel = [UILabel new]; 31 | _timeLabel.textColor = [UIColor yellowColor]; 32 | _timeLabel.font = [UIFont boldSystemFontOfSize:40]; 33 | 34 | [self addSubview:_watermarkLabel]; 35 | [self addSubview:_timeLabel]; 36 | } 37 | 38 | return self; 39 | } 40 | 41 | - (void)layoutSubviews { 42 | [super layoutSubviews]; 43 | 44 | static const CGFloat inset = 8; 45 | 46 | CGSize size = self.bounds.size; 47 | 48 | [_watermarkLabel sizeToFit]; 49 | CGRect watermarkFrame = _watermarkLabel.frame; 50 | watermarkFrame.origin.x = size.width - watermarkFrame.size.width - inset; 51 | watermarkFrame.origin.y = size.height - watermarkFrame.size.height - inset; 52 | _watermarkLabel.frame = watermarkFrame; 53 | 54 | [_timeLabel sizeToFit]; 55 | CGRect timeLabelFrame = _timeLabel.frame; 56 | timeLabelFrame.origin.y = inset; 57 | timeLabelFrame.origin.x = inset; 58 | _timeLabel.frame = timeLabelFrame; 59 | } 60 | 61 | - (void)updateWithVideoTime:(NSTimeInterval)time { 62 | NSDate *currentDate = [self.date dateByAddingTimeInterval:time]; 63 | _timeLabel.text = [NSString stringWithFormat:@"%@", currentDate]; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /Library/Sources/SCVideoPlayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoPlayerView.h 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/30/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCPlayer.h" 11 | #import "SCImageView.h" 12 | 13 | @class SCVideoPlayerView; 14 | 15 | @protocol SCVideoPlayerViewDelegate 16 | 17 | - (void)videoPlayerViewTappedToPlay:(SCVideoPlayerView *__nonnull)videoPlayerView; 18 | 19 | - (void)videoPlayerViewTappedToPause:(SCVideoPlayerView *__nonnull)videoPlayerView; 20 | 21 | @end 22 | 23 | @interface SCVideoPlayerView : UIView 24 | 25 | /** 26 | The delegate 27 | */ 28 | @property (weak, nonatomic) IBOutlet __nullable id delegate; 29 | 30 | /** 31 | The player this SCVideoPlayerView show 32 | */ 33 | @property (strong, nonatomic) SCPlayer *__nullable player; 34 | 35 | /** 36 | The underlying AVPlayerLayer used for displaying the video. 37 | */ 38 | @property (readonly, nonatomic) AVPlayerLayer *__nullable playerLayer; 39 | 40 | /** 41 | If enabled, tapping on the view will pause/unpause the player. 42 | */ 43 | @property (assign, nonatomic) BOOL tapToPauseEnabled; 44 | 45 | /** 46 | Init the SCVideoPlayerView with a provided SCPlayer. 47 | */ 48 | - (nonnull instancetype)initWithPlayer:(SCPlayer *__nonnull)player; 49 | 50 | /** 51 | Set whether every new instances of SCVideoPlayerView should automatically create 52 | and hold an SCPlayer when needed. If disabled, an external SCPlayer must be set 53 | manually to each SCVideoPlayerView instance in order to work properly. Default is YES. 54 | */ 55 | + (void)setAutoCreatePlayerWhenNeeded:(BOOL)autoCreatePlayerWhenNeeded; 56 | 57 | /** 58 | Whether every new instances of SCVideoPlayerView should automatically create and hold an SCPlayer 59 | when needed. If disabled, an external SCPlayer must be set manually to each 60 | SCVideoPlayerView instance in order to work properly. Default is YES. 61 | */ 62 | + (BOOL)autoCreatePlayerWhenNeeded; 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // VRAppDelegate.m 3 | // VideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/3/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCAppDelegate.h" 10 | 11 | @implementation SCAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | return YES; 16 | } 17 | 18 | - (void)applicationWillResignActive:(UIApplication *)application 19 | { 20 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 21 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 22 | } 23 | 24 | - (void)applicationDidEnterBackground:(UIApplication *)application 25 | { 26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | - (void)applicationWillEnterForeground:(UIApplication *)application 31 | { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | - (void)applicationDidBecomeActive:(UIApplication *)application 36 | { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | - (void)applicationWillTerminate:(UIApplication *)application 41 | { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Library/Sources/SCSwipeableFilterView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilterSwitcherView.h 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 29/05/14. 6 | // 7 | // 8 | 9 | #import 10 | #import "SCPlayer.h" 11 | #import "SCFilterImageView.h" 12 | 13 | @class SCSwipeableFilterView; 14 | @protocol SCSwipeableFilterViewDelegate 15 | 16 | - (void)swipeableFilterView:(SCSwipeableFilterView *__nonnull)swipeableFilterView didScrollToFilter:(SCFilter *__nullable)filter; 17 | 18 | @end 19 | 20 | /** 21 | A filter selector view that works like the Snapchat presentation of the available filters. 22 | Filters are swipeable from horizontally. 23 | */ 24 | @interface SCSwipeableFilterView : SCImageView 25 | 26 | /** 27 | The available filterGroups that this SCFilterSwitcherView shows 28 | If you want to show an empty filter (no processing), just add a [NSNull null] 29 | entry instead of an instance of SCFilterGroup 30 | */ 31 | @property (strong, nonatomic) NSArray *__nullable filters; 32 | 33 | /** 34 | The currently selected filter group. 35 | This changes when scrolling in the underlying UIScrollView. 36 | This value is Key-Value observable. 37 | */ 38 | @property (strong, nonatomic) SCFilter *__nullable selectedFilter; 39 | 40 | /** 41 | A filter that is applied before applying the selected filter 42 | */ 43 | @property (strong, nonatomic) SCFilter *__nullable preprocessingFilter; 44 | 45 | /** 46 | The delegate that will receive messages 47 | */ 48 | @property (weak, nonatomic) id __nullable delegate; 49 | 50 | /** 51 | The underlying scrollView used for scrolling between filterGroups. 52 | You can freely add your views inside. 53 | */ 54 | @property (readonly, nonatomic) UIScrollView *__nonnull selectFilterScrollView; 55 | 56 | /** 57 | Whether the current image should be redraw with the new contentOffset 58 | when the UIScrollView is scrolled. If disabled, scrolling will never 59 | show up the other filters, until it receives a new CIImage. 60 | On some device it seems better to disable it when the SCSwipeableFilterView 61 | is set inside a SCPlayer. 62 | Default is YES 63 | */ 64 | @property (assign, nonatomic) BOOL refreshAutomaticallyWhenScrolling; 65 | 66 | /** 67 | Scrolls to a specific filter 68 | */ 69 | - (void)scrollToFilter:(SCFilter *__nonnull)filter animated:(BOOL)animated; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /Library/Sources/SCRecordSessionSegment.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecordSessionSegment.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 10/03/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @interface SCRecordSessionSegment : NSObject 14 | 15 | /** 16 | The url containing the segment data 17 | */ 18 | @property (strong, nonatomic) NSURL *__nonnull url; 19 | 20 | /** 21 | The AVAsset created from the url. 22 | */ 23 | @property (readonly, nonatomic) AVAsset *__nullable asset; 24 | 25 | /** 26 | The duration of this segment 27 | */ 28 | @property (readonly, nonatomic) CMTime duration; 29 | 30 | /** 31 | The thumbnail that represents this segment 32 | */ 33 | @property (readonly, nonatomic) UIImage *__nullable thumbnail; 34 | 35 | /** 36 | The lastImage of this segment. This can be used for implement 37 | features like Vine's ghost mode. 38 | */ 39 | @property (readonly, nonatomic) UIImage *__nullable lastImage; 40 | 41 | /** 42 | The average frameRate of this segment 43 | */ 44 | @property (readonly, nonatomic) float frameRate; 45 | 46 | /** 47 | The custom info dictionary. 48 | */ 49 | @property (readonly, nonatomic) NSDictionary *__nullable info; 50 | 51 | /** 52 | Whether the file at the url exists 53 | */ 54 | @property (readonly, nonatomic) BOOL fileUrlExists; 55 | 56 | /** 57 | Initialize with an URL and an info dictionary 58 | */ 59 | - (nonnull instancetype)initWithURL:(NSURL *__nonnull)url info:(NSDictionary *__nullable)info; 60 | 61 | /** 62 | Initialize from a dictionaryRepresentation 63 | */ 64 | - (nullable instancetype)initWithDictionaryRepresentation:(NSDictionary *__nonnull)dictionary directory:(NSString *__nonnull)directory; 65 | 66 | /** 67 | Delete the file at the url. This will make the segment unusable. 68 | */ 69 | - (void)deleteFile; 70 | 71 | - (NSDictionary *__nonnull)dictionaryRepresentation; 72 | 73 | /** 74 | Returns a record segment URL for a filename and a directory. 75 | */ 76 | + (NSURL *__nonnull)segmentURLForFilename:(NSString *__nonnull)filename andDirectory:(NSString *__nonnull)directory; 77 | 78 | /** 79 | Create and init a segment using an URL and an info dictionary 80 | */ 81 | + (SCRecordSessionSegment *__nonnull)segmentWithURL:(NSURL *__nonnull)url info:(NSDictionary *__nullable)info; 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Library/Sources/SCSaveToCameraRollOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCSaveToCameraRollOperation.m 3 | // SCRecorder 4 | // 5 | // Created by Simon Corsin on 10/12/15. 6 | // Copyright © 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCSaveToCameraRollOperation.h" 11 | 12 | @interface SCSaveToCameraRollOperation () 13 | 14 | @property (strong, nonatomic) void (^videoCompletion)(NSString *, NSError *); 15 | @property (strong, nonatomic) void (^imageCompletion)(NSError *); 16 | 17 | @end 18 | 19 | @implementation SCSaveToCameraRollOperation 20 | 21 | #pragma mark - Public API 22 | 23 | - (void)saveVideoURL:(NSURL *)url completion:(void (^)(NSString *, NSError *))completion { 24 | self.videoCompletion = completion; 25 | [self _didStart]; 26 | 27 | UISaveVideoAtPathToSavedPhotosAlbum(url.path, self, @selector(video:didFinishSavingWithError:contextInfo:), nil); 28 | } 29 | 30 | - (void)saveImage:(UIImage *)image completion:(void (^)(NSError *))completion { 31 | self.imageCompletion = completion; 32 | [self _didStart]; 33 | 34 | UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); 35 | } 36 | 37 | #pragma mark - Save completions 38 | 39 | - (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { 40 | [self _didEnd]; 41 | 42 | void (^completion)(NSString *, NSError *) = self.videoCompletion; 43 | self.videoCompletion = nil; 44 | 45 | if (completion != nil) { 46 | completion(videoPath, error); 47 | } 48 | } 49 | 50 | - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { 51 | [self _didEnd]; 52 | 53 | void (^completion)(NSError *) = self.imageCompletion; 54 | self.imageCompletion = nil; 55 | 56 | if (completion != nil) { 57 | completion(error); 58 | } 59 | } 60 | 61 | #pragma mark - Private API 62 | 63 | static NSMutableArray *pendingOperations = nil; 64 | 65 | - (void)_didStart { 66 | static dispatch_once_t onceToken; 67 | dispatch_once(&onceToken, ^{ 68 | pendingOperations = [NSMutableArray new]; 69 | }); 70 | 71 | @synchronized(pendingOperations) { 72 | [pendingOperations addObject:self]; 73 | } 74 | } 75 | 76 | - (void)_didEnd { 77 | @synchronized(pendingOperations) { 78 | [pendingOperations removeObject:self]; 79 | } 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Library/Sources/SCAudioConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCAudioConfiguration.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 21/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCAudioConfiguration.h" 10 | 11 | @implementation SCAudioConfiguration 12 | 13 | - (id)init { 14 | self = [super init]; 15 | 16 | if (self) { 17 | self.bitrate = kSCAudioConfigurationDefaultBitrate; 18 | _format = kSCAudioConfigurationDefaultAudioFormat; 19 | } 20 | 21 | return self; 22 | } 23 | 24 | - (NSDictionary *)createAssetWriterOptionsUsingSampleBuffer:(CMSampleBufferRef)sampleBuffer { 25 | NSDictionary *options = self.options; 26 | if (options != nil) { 27 | return options; 28 | } 29 | 30 | Float64 sampleRate = self.sampleRate; 31 | int channels = self.channelsCount; 32 | unsigned long bitrate = (unsigned long)self.bitrate; 33 | 34 | if (self.preset != nil) { 35 | if ([self.preset isEqualToString:SCPresetLowQuality]) { 36 | bitrate = 64000; 37 | channels = 1; 38 | } else if ([self.preset isEqualToString:SCPresetMediumQuality]) { 39 | bitrate = 128000; 40 | } else if ([self.preset isEqualToString:SCPresetHighestQuality]) { 41 | bitrate = 320000; 42 | } else { 43 | NSLog(@"Unrecognized video preset %@", self.preset); 44 | } 45 | } 46 | 47 | if (sampleBuffer != nil) { 48 | CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer); 49 | const AudioStreamBasicDescription *streamBasicDescription = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription); 50 | 51 | if (sampleRate == 0) { 52 | sampleRate = streamBasicDescription->mSampleRate; 53 | } 54 | if (channels == 0) { 55 | channels = streamBasicDescription->mChannelsPerFrame; 56 | } 57 | } 58 | 59 | if (sampleRate == 0) { 60 | sampleRate = kSCAudioConfigurationDefaultSampleRate; 61 | } 62 | if (channels == 0) { 63 | channels = kSCAudioConfigurationDefaultNumberOfChannels; 64 | } 65 | 66 | return @{ 67 | AVFormatIDKey : [NSNumber numberWithInt: self.format], 68 | AVEncoderBitRateKey : [NSNumber numberWithUnsignedLong: bitrate], 69 | AVNumberOfChannelsKey : [NSNumber numberWithInt: channels], 70 | AVSampleRateKey : [NSNumber numberWithInt: sampleRate] 71 | }; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCImageDisplayerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCImageViewDisPlayViewController.m 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by 曾 宪华 on 13-11-5. 6 | // Copyright (c) 2013年 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCImageDisplayerViewController.h" 10 | 11 | @interface SCImageDisplayerViewController () { 12 | } 13 | @end 14 | 15 | @implementation SCImageDisplayerViewController 16 | 17 | - (void)viewWillAppear:(BOOL)animated { 18 | [super viewWillAppear:animated]; 19 | self.navigationController.navigationBarHidden = NO; 20 | 21 | [self.filterSwitcherView setImageByUIImage:self.photo]; 22 | } 23 | 24 | - (void)viewWillDisappear:(BOOL)animated { 25 | [super viewWillDisappear:animated]; 26 | self.navigationController.navigationBarHidden = YES; 27 | } 28 | 29 | - (void)viewDidLayoutSubviews { 30 | [super viewDidLayoutSubviews]; 31 | 32 | [self.filterSwitcherView setNeedsDisplay]; 33 | } 34 | 35 | - (void)viewDidLoad 36 | { 37 | [super viewDidLoad]; 38 | 39 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Save" style:UIBarButtonItemStylePlain target:self action:@selector(saveToCameraRoll)]; 40 | 41 | self.filterSwitcherView.contentMode = UIViewContentModeScaleAspectFit; 42 | 43 | self.filterSwitcherView.filters = @[ 44 | [SCFilter emptyFilter], 45 | [SCFilter filterWithCIFilterName:@"CIPhotoEffectNoir"], 46 | [SCFilter filterWithCIFilterName:@"CIPhotoEffectChrome"], 47 | [SCFilter filterWithCIFilterName:@"CIPhotoEffectInstant"], 48 | [SCFilter filterWithCIFilterName:@"CIPhotoEffectTonal"], 49 | [SCFilter filterWithCIFilterName:@"CIPhotoEffectFade"] 50 | ]; 51 | } 52 | 53 | - (void)saveToCameraRoll { 54 | UIImage *image = [self.filterSwitcherView renderedUIImage]; 55 | 56 | [image saveToCameraRollWithCompletion:^(NSError * _Nullable error) { 57 | if (error == nil) { 58 | [[[UIAlertView alloc] initWithTitle:@"Done!" message:@"" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; 59 | } else { 60 | [[[UIAlertView alloc] initWithTitle:@"Failed :(" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; 61 | } 62 | }]; 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /Library/Sources/SCFilterAnimation.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilterAnimation.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 06/05/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCFilterAnimation.h" 10 | 11 | @interface SCFilterAnimation() { 12 | 13 | } 14 | 15 | @end 16 | 17 | @implementation SCFilterAnimation 18 | 19 | - (void)encodeWithCoder:(NSCoder *)aCoder { 20 | [aCoder encodeObject:_key forKey:@"Key"]; 21 | [aCoder encodeObject:_startValue forKey:@"StartValue"]; 22 | [aCoder encodeObject:_endValue forKey:@"EndValue"]; 23 | [aCoder encodeDouble:_startTime forKey:@"StartTime"]; 24 | [aCoder encodeDouble:_duration forKey:@"Duration"]; 25 | } 26 | 27 | - (id)initWithCoder:(NSCoder *)aDecoder { 28 | NSString *key = [aDecoder decodeObjectForKey:@"Key"]; 29 | id startValue = [aDecoder decodeObjectForKey:@"StartValue"]; 30 | id endValue = [aDecoder decodeObjectForKey:@"EndValue"]; 31 | CFTimeInterval startTime = [aDecoder decodeDoubleForKey:@"StartTime"]; 32 | CFTimeInterval duration = [aDecoder decodeDoubleForKey:@"Duration"]; 33 | 34 | return [self initWithKey:key startValue:startValue endValue:endValue startTime:startTime duration:duration]; 35 | } 36 | 37 | - (id)initWithKey:(NSString *)key startValue:(id)startValue endValue:(id)endValue startTime:(CFTimeInterval)startTime duration:(CFTimeInterval)duration { 38 | self = [self init]; 39 | 40 | if (self) { 41 | _key = key; 42 | _startValue = startValue; 43 | _endValue = endValue; 44 | _startTime = startTime; 45 | _duration = duration; 46 | 47 | if ([startValue isKindOfClass:[NSNumber class]]) { 48 | 49 | } else { 50 | [NSException raise:@"InvalidArgumentException" format:@"Only values as NSNumber's are currently supported"]; 51 | } 52 | } 53 | 54 | return self; 55 | } 56 | 57 | - (id)valueAtTime:(CFTimeInterval)time { 58 | double ratio = (time - _startTime) / _duration; 59 | double newValue = ([_endValue doubleValue] - [_startValue doubleValue]) * ratio + [_startValue doubleValue]; 60 | 61 | return [NSNumber numberWithDouble:newValue]; 62 | } 63 | 64 | - (BOOL)hasValueAtTime:(CFTimeInterval)time { 65 | return !(time < _startTime || time > _startTime + _duration); 66 | } 67 | 68 | + (SCFilterAnimation *)filterAnimationForParameterKey:(NSString *)key startValue:(id)startValue endValue:(id)endValue startTime:(CFTimeInterval)startTime duration:(CFTimeInterval)duration { 69 | return [[SCFilterAnimation alloc] initWithKey:key startValue:startValue endValue:endValue startTime:startTime duration:duration]; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /Library/Sources/SCContext.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCContext.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 28/05/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | typedef NS_ENUM(NSInteger, SCContextType) { 14 | 15 | /** 16 | Automatically choose an appropriate SCContext context 17 | */ 18 | SCContextTypeAuto, 19 | 20 | /** 21 | Create a hardware accelerated SCContext with Metal 22 | */ 23 | SCContextTypeMetal, 24 | 25 | /** 26 | Create a hardware accelerated SCContext with CoreGraphics 27 | */ 28 | SCContextTypeCoreGraphics, 29 | 30 | /** 31 | Create a hardware accelerated SCContext with EAGL (OpenGL) 32 | */ 33 | SCContextTypeEAGL, 34 | 35 | /** 36 | Creates a standard SCContext hardware accelerated. 37 | */ 38 | SCContextTypeDefault, 39 | 40 | /** 41 | Create a software rendered SCContext (no hardware acceleration) 42 | */ 43 | SCContextTypeCPU 44 | }; 45 | 46 | extern NSString *__nonnull const SCContextOptionsCGContextKey; 47 | extern NSString *__nonnull const SCContextOptionsEAGLContextKey; 48 | extern NSString *__nonnull const SCContextOptionsMTLDeviceKey; 49 | 50 | /** 51 | Simple abstraction over CIContext. 52 | */ 53 | @interface SCContext : NSObject 54 | 55 | /** 56 | The CIContext 57 | */ 58 | @property (readonly, nonatomic) CIContext *__nonnull CIContext; 59 | 60 | /** 61 | The type with with which this SCContext was created 62 | */ 63 | @property (readonly, nonatomic) SCContextType type; 64 | 65 | /** 66 | Will be non null if the type is SCContextTypeEAGL 67 | */ 68 | @property (readonly, nonatomic) EAGLContext *__nullable EAGLContext; 69 | 70 | /** 71 | Will be non null if the type is SCContextTypeMetal 72 | */ 73 | @property (readonly, nonatomic) id __nullable MTLDevice; 74 | 75 | /** 76 | Will be non null if the type is SCContextTypeCoreGraphics 77 | */ 78 | @property (readonly, nonatomic) CGContextRef __nullable CGContext; 79 | 80 | /** 81 | Create and returns a new context with the given type. You must check 82 | whether the contextType is supported by calling +[SCContext supportsType:] before. 83 | */ 84 | + (SCContext *__nonnull)contextWithType:(SCContextType)type options:(NSDictionary *__nullable)options; 85 | 86 | /** 87 | Returns whether the contextType can be safely created and used using +[SCContext contextWithType:] 88 | */ 89 | + (BOOL)supportsType:(SCContextType)contextType; 90 | 91 | /** 92 | The context that will be used when using an Auto context type; 93 | */ 94 | + (SCContextType)suggestedContextType; 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /Library/Sources/SCRecordSession_Internal.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecordSession_Internal.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 24/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCRecorder.h" 10 | #import "SCRecordSession.h" 11 | 12 | @interface SCRecordSession() { 13 | AVAssetWriter *_assetWriter; 14 | AVAssetWriterInput *_videoInput; 15 | AVAssetWriterInput *_audioInput; 16 | NSMutableArray *_segments; 17 | BOOL _audioInitializationFailed; 18 | BOOL _videoInitializationFailed; 19 | BOOL _recordSegmentReady; 20 | BOOL _currentSegmentHasVideo; 21 | BOOL _currentSegmentHasAudio; 22 | 23 | int _currentSegmentCount; 24 | CMTime _timeOffset; 25 | CMTime _lastTimeAudio; 26 | CMTime _currentSegmentDuration; 27 | CMTime _sessionStartTime; 28 | 29 | SCVideoConfiguration *_videoConfiguration; 30 | SCAudioConfiguration *_audioConfiguration; 31 | 32 | AVAssetWriterInputPixelBufferAdaptor *_videoPixelBufferAdaptor; 33 | CMTime _lastTimeVideo; 34 | 35 | dispatch_queue_t _audioQueue; 36 | 37 | // Used when the fastRecordMethod is enabled 38 | AVCaptureMovieFileOutput *_movieFileOutput; 39 | } 40 | 41 | @property (weak, nonatomic) SCRecorder *recorder; 42 | 43 | @property (readonly, nonatomic) BOOL videoInitialized; 44 | @property (readonly, nonatomic) BOOL audioInitialized; 45 | @property (readonly, nonatomic) BOOL videoInitializationFailed; 46 | @property (readonly, nonatomic) BOOL audioInitializationFailed; 47 | @property (readonly, nonatomic) BOOL recordSegmentReady; 48 | @property (readonly, nonatomic) BOOL currentSegmentHasAudio; 49 | @property (readonly, nonatomic) BOOL currentSegmentHasVideo; 50 | @property (readonly, nonatomic) BOOL isUsingMovieFileOutput; 51 | 52 | - (void)initializeVideo:(NSDictionary *)videoOptions formatDescription:(CMFormatDescriptionRef)formatDescription error:(NSError **)error; 53 | - (void)initializeAudio:(NSDictionary *)audioOptions formatDescription:(CMFormatDescriptionRef)formatDescription error:(NSError **)error; 54 | 55 | - (CVPixelBufferRef)createPixelBuffer; 56 | 57 | - (void)appendVideoPixelBuffer:(CVPixelBufferRef)videoSampleBuffer atTime:(CMTime)time duration:(CMTime)duration completion:(void(^)(BOOL success))completion; 58 | 59 | - (void)appendAudioSampleBuffer:(CMSampleBufferRef)audioSampleBuffer completion:(void(^)(BOOL success))completion; 60 | 61 | 62 | - (void)beginRecordSegmentUsingMovieFileOutput:(AVCaptureMovieFileOutput *)movieFileOutput error:(NSError **)error delegate:(id)delegate; 63 | 64 | - (void)notifyMovieFileOutputIsReady; 65 | 66 | - (void)appendRecordSegmentUrl:(NSURL *)url info:(NSDictionary *)info error:(NSError *)error completionHandler:(void(^)(SCRecordSessionSegment *segment, NSError* error))completionHandler; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderToolsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderToolsView.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 16/02/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCRecorder.h" 11 | 12 | //typedef enum : NSUInteger { 13 | // SCRecorderToolsViewShowFocusModeNever, 14 | // SCRecorderToolsViewShowFocusModeOnlyOnSubjectChanged, 15 | // SCRecorderToolsViewShowFocusModeAlways 16 | //} SCRecorderToolsViewShowFocusMode; 17 | 18 | @class SCRecorder; 19 | @class SCRecorderToolsView; 20 | 21 | @protocol SCRecorderToolsViewDelegate 22 | 23 | @optional 24 | 25 | - (void)recorderToolsView:(SCRecorderToolsView *__nonnull)recorderToolsView didTapToFocusWithGestureRecognizer:(UIGestureRecognizer *__nonnull)gestureRecognizer; 26 | 27 | @end 28 | 29 | @interface SCRecorderToolsView : UIView 30 | 31 | @property (nonatomic, weak) __nullable id delegate; 32 | 33 | /** 34 | The instance of the SCRecorder to use. 35 | */ 36 | @property (strong, nonatomic) SCRecorder *__nullable recorder; 37 | 38 | /** 39 | The outside image used when focusing. 40 | */ 41 | @property (strong, nonatomic) UIImage *__nullable outsideFocusTargetImage; 42 | 43 | /** 44 | The inside image used when focusing. 45 | */ 46 | @property (strong, nonatomic) UIImage *__nullable insideFocusTargetImage; 47 | 48 | /** 49 | The size of the focus target. 50 | */ 51 | @property (assign, nonatomic) CGSize focusTargetSize; 52 | 53 | /** 54 | The minimum zoom allowed for the pinch to zoom. 55 | Default is 1 56 | */ 57 | @property (assign, nonatomic) CGFloat minZoomFactor; 58 | 59 | /** 60 | The maximum zoom allowed for the pinch to zoom. 61 | Default is 4 62 | */ 63 | @property (assign, nonatomic) CGFloat maxZoomFactor; 64 | 65 | /** 66 | Whether the tap to focus should be enabled. 67 | */ 68 | @property (assign, nonatomic) BOOL tapToFocusEnabled; 69 | 70 | /** 71 | Whether the double tap to reset the focus should be enabled. 72 | */ 73 | @property (assign, nonatomic) BOOL doubleTapToResetFocusEnabled; 74 | 75 | /** 76 | Whether the pinch to zoom should be enabled. 77 | */ 78 | @property (assign, nonatomic) BOOL pinchToZoomEnabled; 79 | 80 | @property (assign, nonatomic) BOOL showsFocusAnimationAutomatically; 81 | 82 | ///** 83 | // When the SCRecorderToolsView should show the focus animation 84 | // when the focusing state changes. If set to Never, you will have to call 85 | // "showFocusAnimation" and "hideFocusAnimation" yourself. 86 | // 87 | // Default is OnlyOnSubjectChange 88 | // */ 89 | //@property (assign, nonatomic) SCRecorderToolsViewShowFocusMode showFocusMode; 90 | 91 | /** 92 | Manually show the focus animation. 93 | This method is called automatically if showsFocusAnimationAutomatically 94 | is set to YES. 95 | */ 96 | - (void)showFocusAnimation; 97 | 98 | /** 99 | Manually hide the focus animation. 100 | This method is called automatically if showsFocusAnimationAutomatically 101 | is set to YES. 102 | */ 103 | - (void)hideFocusAnimation; 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCRecordSessionManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecordSessionManager.m 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 15/08/14. 6 | // 7 | // 8 | 9 | #import "SCRecordSessionManager.h" 10 | #define kUserDefaultsStorageKey @"RecordSessions" 11 | 12 | @implementation SCRecordSessionManager 13 | 14 | - (void)modifyMetadatas:(void(^)(NSMutableArray *metadatas))block { 15 | NSMutableArray *metadatas = [[self savedRecordSessions] mutableCopy]; 16 | 17 | if (metadatas == nil) { 18 | metadatas = [NSMutableArray new]; 19 | } 20 | 21 | block(metadatas); 22 | 23 | [[NSUserDefaults standardUserDefaults] setObject:metadatas forKey:kUserDefaultsStorageKey]; 24 | } 25 | 26 | - (void)saveRecordSession:(SCRecordSession *)recordSession { 27 | [self modifyMetadatas:^(NSMutableArray *metadatas) { 28 | 29 | NSInteger insertIndex = -1; 30 | 31 | for (int i = 0; i < metadatas.count; i++) { 32 | NSDictionary *otherRecordSessionMetadata = [metadatas objectAtIndex:i]; 33 | if ([otherRecordSessionMetadata[SCRecordSessionIdentifierKey] isEqualToString:recordSession.identifier]) { 34 | insertIndex = i; 35 | break; 36 | } 37 | } 38 | 39 | NSDictionary *metadata = recordSession.dictionaryRepresentation; 40 | 41 | if (insertIndex == -1) { 42 | [metadatas addObject:metadata]; 43 | } else { 44 | [metadatas replaceObjectAtIndex:insertIndex withObject:metadata]; 45 | } 46 | }]; 47 | } 48 | 49 | - (void)removeRecordSession:(SCRecordSession *)recordSession { 50 | [self modifyMetadatas:^(NSMutableArray *metadatas) { 51 | 52 | for (int i = 0; i < metadatas.count; i++) { 53 | NSDictionary *otherRecordSessionMetadata = [metadatas objectAtIndex:i]; 54 | if ([otherRecordSessionMetadata[SCRecordSessionIdentifierKey] isEqualToString:recordSession.identifier]) { 55 | i--; 56 | [metadatas removeObjectAtIndex:i]; 57 | break; 58 | } 59 | } 60 | }]; 61 | } 62 | 63 | - (BOOL)isSaved:(SCRecordSession *)recordSession { 64 | NSArray *sessions = [self savedRecordSessions]; 65 | 66 | for (NSDictionary *session in sessions) { 67 | if ([session[SCRecordSessionIdentifierKey] isEqualToString:recordSession.identifier]) { 68 | return YES; 69 | } 70 | } 71 | 72 | return NO; 73 | } 74 | 75 | - (void)removeRecordSessionAtIndex:(NSInteger)index { 76 | [self modifyMetadatas:^(NSMutableArray *metadatas) { 77 | [metadatas removeObjectAtIndex:index]; 78 | }]; 79 | } 80 | 81 | - (NSArray *)savedRecordSessions { 82 | return [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsStorageKey]; 83 | } 84 | 85 | static SCRecordSessionManager *_sharedInstance; 86 | 87 | + (SCRecordSessionManager *)sharedInstance { 88 | static dispatch_once_t onceToken; 89 | dispatch_once(&onceToken, ^{ 90 | _sharedInstance = [SCRecordSessionManager new]; 91 | }); 92 | 93 | return _sharedInstance; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /Library/Sources/SCImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCCIImageView.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 14/05/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import "SCFilter.h" 15 | #import "SCContext.h" 16 | 17 | /** 18 | A view capable of rendering CIImages. 19 | */ 20 | @interface SCImageView : UIView 21 | 22 | /** 23 | The context type to use when loading the context. 24 | */ 25 | @property (assign, nonatomic) SCContextType contextType; 26 | 27 | /** 28 | The SCContext that hold the underlying CIContext for rendering the CIImage's 29 | Will be automatically loaded when setting the first CIImage or when rendering 30 | for the first if using a CoreGraphics context type. 31 | You can also set your own context. 32 | Supported contexts are Metal, CoreGraphics, EAGL 33 | */ 34 | @property (strong, nonatomic) SCContext *__nullable context; 35 | 36 | /** 37 | The CIImage to render. 38 | */ 39 | @property (strong, nonatomic) CIImage *__nullable CIImage; 40 | 41 | /** 42 | The timestamp of the CIImage 43 | */ 44 | @property (assign, nonatomic) CFTimeInterval CIImageTime; 45 | 46 | /** 47 | The preferred transform for rendering the CIImage 48 | */ 49 | @property (assign, nonatomic) CGAffineTransform preferredCIImageTransform; 50 | 51 | /** 52 | Whether the CIImage should be scaled and resized according to the contentMode of this view. 53 | Default is YES. 54 | */ 55 | @property (assign, nonatomic) BOOL scaleAndResizeCIImageAutomatically; 56 | 57 | /** 58 | Set the CIImage using a sampleBuffer. The CIImage will be automatically generated 59 | when needed. This avoids creating multiple CIImage if the SCImageView can't render them 60 | as fast. 61 | */ 62 | - (void)setImageBySampleBuffer:(__nonnull CMSampleBufferRef)sampleBuffer; 63 | 64 | /** 65 | Set the CIImage using an UIImage 66 | */ 67 | - (void)setImageByUIImage:(UIImage *__nullable)image; 68 | 69 | /** 70 | Create the CIContext and setup the underlying rendering views. This is automatically done when setting an CIImage 71 | for the first time to make the initialization faster. If for some reasons you want it to be done earlier 72 | you can call this method. 73 | Returns whether the context has been successfully loaded, returns NO otherwise. 74 | */ 75 | - (BOOL)loadContextIfNeeded; 76 | 77 | /** 78 | Returns the rendered CIImage in the given rect. 79 | Subclass can override this method to alterate the rendered image. 80 | */ 81 | - (CIImage *__nullable)renderedCIImageInRect:(CGRect)rect; 82 | 83 | /** 84 | Returns the rendered CIImage in the given rect. 85 | It internally calls renderedCIImageInRect: 86 | Subclass should not override this method. 87 | */ 88 | - (UIImage *__nullable)renderedUIImageInRect:(CGRect)rect; 89 | 90 | /** 91 | Returns the rendered CIImage in its natural size. 92 | Subclass should not override this method. 93 | */ 94 | - (CIImage *__nullable)renderedCIImage; 95 | 96 | /** 97 | Returns the rendered UIImage in its natural size. 98 | Subclass should not override this method. 99 | */ 100 | - (UIImage *__nullable)renderedUIImage; 101 | 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Library/Sources/SCAssetExportSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCAssetExportSession.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 14/05/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SCFilter.h" 12 | #import "SCVideoConfiguration.h" 13 | #import "SCAudioConfiguration.h" 14 | #import "SCContext.h" 15 | 16 | @class SCAssetExportSession; 17 | @protocol SCAssetExportSessionDelegate 18 | 19 | @optional 20 | 21 | - (void)assetExportSessionDidProgress:(SCAssetExportSession *__nonnull)assetExportSession; 22 | 23 | - (BOOL)assetExportSession:(SCAssetExportSession *__nonnull)assetExportSession shouldReginReadWriteOnInput:(AVAssetWriterInput *__nonnull)writerInput fromOutput:(AVAssetReaderOutput *__nonnull)output; 24 | 25 | - (BOOL)assetExportSessionNeedsInputPixelBufferAdaptor:(SCAssetExportSession *__nonnull)assetExportSession; 26 | 27 | @end 28 | 29 | @interface SCAssetExportSession : NSObject 30 | 31 | /** 32 | The input asset to use 33 | */ 34 | @property (strong, nonatomic) AVAsset *__nullable inputAsset; 35 | 36 | /** 37 | The outputUrl to which the asset will be exported 38 | */ 39 | @property (strong, nonatomic) NSURL *__nullable outputUrl; 40 | 41 | /** 42 | The type of file to be written by the export session 43 | */ 44 | @property (strong, nonatomic) NSString *__nullable outputFileType; 45 | 46 | /** 47 | The context type to use for rendering the images through a filter 48 | */ 49 | @property (assign, nonatomic) SCContextType contextType; 50 | 51 | /** 52 | Access the configuration for the video. 53 | */ 54 | @property (readonly, nonatomic) SCVideoConfiguration *__nonnull videoConfiguration; 55 | 56 | /** 57 | Access the configuration for the audio. 58 | */ 59 | @property (readonly, nonatomic) SCAudioConfiguration *__nonnull audioConfiguration; 60 | 61 | /** 62 | If an error occurred during the export, this will contain that error 63 | */ 64 | @property (readonly, nonatomic) NSError *__nullable error; 65 | 66 | /** 67 | Will be set to YES if cancelExport was called 68 | */ 69 | @property (readonly, atomic) BOOL cancelled; 70 | 71 | /** 72 | The timeRange to read from the inputAsset 73 | */ 74 | @property (assign, nonatomic) CMTimeRange timeRange; 75 | 76 | /** 77 | Whether the assetExportSession should automatically translate the filter into an AVVideoComposition. 78 | This won't be done if a composition has already been set in the videoConfiguration. 79 | Default is YES 80 | */ 81 | @property (assign, nonatomic) BOOL translatesFilterIntoComposition; 82 | 83 | /** 84 | Indicates whether the movie should be optimized for network use. 85 | Default is NO 86 | */ 87 | @property (assign, nonatomic) BOOL shouldOptimizeForNetworkUse; 88 | 89 | /** 90 | The current progress 91 | */ 92 | @property (readonly, nonatomic) float progress; 93 | 94 | @property (weak, nonatomic) __nullable id delegate; 95 | 96 | - (nonnull instancetype)init; 97 | 98 | // Init with the inputAsset 99 | - (nonnull instancetype)initWithAsset:(AVAsset *__nonnull)inputAsset; 100 | 101 | /** 102 | Cancels exportAsynchronouslyWithCompletionHandler 103 | */ 104 | - (void)cancelExport; 105 | 106 | /** 107 | Starts the asynchronous execution of the export session 108 | */ 109 | - (void)exportAsynchronouslyWithCompletionHandler:(void(^__nullable)())completionHandler; 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/Launch Screen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCEditVideoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCEditVideoViewController.m 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 22/07/14. 6 | // 7 | // 8 | 9 | #import "SCEditVideoViewController.h" 10 | 11 | @interface SCEditVideoViewController () { 12 | NSMutableArray *_thumbnails; 13 | NSInteger _currentSelected; 14 | } 15 | 16 | @end 17 | 18 | @implementation SCEditVideoViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.videoPlayerView.tapToPauseEnabled = YES; 24 | self.videoPlayerView.player.loopEnabled = YES; 25 | } 26 | 27 | - (void)viewWillAppear:(BOOL)animated { 28 | [super viewWillAppear:animated]; 29 | 30 | NSMutableArray *thumbnails = [NSMutableArray new]; 31 | NSInteger i = 0; 32 | 33 | for (SCRecordSessionSegment *segment in self.recordSession.segments) { 34 | UIImageView *imageView = [[UIImageView alloc] init]; 35 | imageView.contentMode = UIViewContentModeScaleAspectFill; 36 | imageView.image = segment.thumbnail; 37 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(touchedVideo:)]; 38 | imageView.userInteractionEnabled = YES; 39 | 40 | [imageView addGestureRecognizer:tapGesture]; 41 | 42 | [thumbnails addObject:imageView]; 43 | 44 | [self.scrollView addSubview:imageView]; 45 | 46 | i++; 47 | } 48 | 49 | _thumbnails = thumbnails; 50 | 51 | [self reloadScrollView]; 52 | [self showVideo:0]; 53 | } 54 | 55 | - (void)viewWillDisappear:(BOOL)animated { 56 | [super viewWillDisappear:animated]; 57 | 58 | [self.videoPlayerView.player pause]; 59 | } 60 | 61 | - (void)touchedVideo:(UITapGestureRecognizer *)gesture { 62 | NSInteger idx = [_thumbnails indexOfObject:gesture.view]; 63 | 64 | [self showVideo:idx]; 65 | } 66 | 67 | - (void)showVideo:(NSInteger)idx { 68 | if (idx < 0) { 69 | idx = 0; 70 | } 71 | 72 | if (idx < _recordSession.segments.count) { 73 | SCRecordSessionSegment *segment = [_recordSession.segments objectAtIndex:idx]; 74 | [self.videoPlayerView.player setItemByAsset:segment.asset]; 75 | [self.videoPlayerView.player play]; 76 | } 77 | 78 | _currentSelected = idx; 79 | 80 | for (NSInteger i = 0; i < _thumbnails.count; i++) { 81 | UIImageView *imageView = [_thumbnails objectAtIndex:i]; 82 | 83 | imageView.alpha = i == idx ? 1 : 0.5; 84 | } 85 | } 86 | 87 | - (void)reloadScrollView { 88 | CGFloat cellSize = self.scrollView.frame.size.height; 89 | int i = 0; 90 | for (UIImageView *imageView in _thumbnails) { 91 | imageView.frame = CGRectMake(cellSize * i, 0, cellSize, cellSize); 92 | i++; 93 | } 94 | self.scrollView.contentSize = CGSizeMake(_thumbnails.count * self.scrollView.frame.size.height, self.scrollView.frame.size.height); 95 | } 96 | 97 | - (IBAction)deletePressed:(id)sender { 98 | if (_currentSelected < _recordSession.segments.count) { 99 | [_recordSession removeSegmentAtIndex:_currentSelected deleteFile:YES]; 100 | UIImageView *imageView = [_thumbnails objectAtIndex:_currentSelected]; 101 | [_thumbnails removeObjectAtIndex:_currentSelected]; 102 | [UIView animateWithDuration:0.3 animations:^{ 103 | imageView.transform = CGAffineTransformMakeScale(0, 0); 104 | [self reloadScrollView]; 105 | } completion:^(BOOL finished) { 106 | [imageView removeFromSuperview]; 107 | }]; 108 | 109 | [self showVideo:_currentSelected % _recordSession.segments.count]; 110 | } 111 | } 112 | @end 113 | -------------------------------------------------------------------------------- /Library/Sources/SCVideoPlayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoPlayerView.m 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/30/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCVideoPlayerView.h" 10 | 11 | //////////////////////////////////////////////////////////// 12 | // PRIVATE DEFINITION 13 | ///////////////////// 14 | 15 | @interface SCVideoPlayerView() { 16 | BOOL _holdPlayer; 17 | UITapGestureRecognizer *_tapToPauseGesture; 18 | } 19 | 20 | @end 21 | 22 | //////////////////////////////////////////////////////////// 23 | // IMPLEMENTATION 24 | ///////////////////// 25 | 26 | @implementation SCVideoPlayerView 27 | 28 | - (id)init { 29 | self = [super init]; 30 | 31 | if (self) { 32 | [self _commonInit:nil]; 33 | } 34 | 35 | return self; 36 | } 37 | 38 | - (id)initWithPlayer:(SCPlayer *)thePlayer { 39 | self = [super init]; 40 | 41 | if (self) { 42 | [self _commonInit:thePlayer]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (void)dealloc { 49 | if (_holdPlayer) { 50 | [self.player pause]; 51 | [self.player endSendingPlayMessages]; 52 | } 53 | } 54 | 55 | - (id)initWithCoder:(NSCoder *)aDecoder { 56 | self = [super initWithCoder:aDecoder]; 57 | 58 | if (self) { 59 | [self _commonInit:nil]; 60 | } 61 | 62 | return self; 63 | } 64 | 65 | - (void)_commonInit:(SCPlayer *)player { 66 | _playerLayer = [AVPlayerLayer new]; 67 | [self.layer insertSublayer:_playerLayer atIndex:0]; 68 | 69 | BOOL holdPlayer = NO; 70 | if (player == nil && [SCVideoPlayerView autoCreatePlayerWhenNeeded]) { 71 | player = [SCPlayer player]; 72 | holdPlayer = YES; 73 | } 74 | self.player = player; 75 | _holdPlayer = holdPlayer; 76 | 77 | self.clipsToBounds = YES; 78 | [self setNeedsLayout]; 79 | } 80 | 81 | - (void)tapOrPause { 82 | id delegate = self.delegate; 83 | 84 | if (!self.player.isPlaying) { 85 | [self.player play]; 86 | 87 | if ([delegate respondsToSelector:@selector(videoPlayerViewTappedToPlay:)]) { 88 | [delegate videoPlayerViewTappedToPlay:self]; 89 | } 90 | } else { 91 | [self.player pause]; 92 | 93 | if ([delegate respondsToSelector:@selector(videoPlayerViewTappedToPause:)]) { 94 | [delegate videoPlayerViewTappedToPause:self]; 95 | } 96 | } 97 | } 98 | 99 | - (void)layoutSubviews { 100 | [super layoutSubviews]; 101 | 102 | _playerLayer.frame = self.bounds; 103 | } 104 | 105 | - (BOOL)tapToPauseEnabled { 106 | return _tapToPauseGesture != nil; 107 | } 108 | 109 | - (void)setTapToPauseEnabled:(BOOL)tapToPauseEnabled { 110 | if (tapToPauseEnabled) { 111 | if (_tapToPauseGesture == nil) { 112 | _tapToPauseGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOrPause)]; 113 | [self addGestureRecognizer:_tapToPauseGesture]; 114 | } 115 | } else { 116 | if (_tapToPauseGesture != nil) { 117 | [_tapToPauseGesture.view removeGestureRecognizer:_tapToPauseGesture]; 118 | _tapToPauseGesture = nil; 119 | } 120 | } 121 | } 122 | 123 | - (void)setPlayer:(SCPlayer *)player { 124 | if (player != _player) { 125 | _player = player; 126 | 127 | _playerLayer.player = player; 128 | 129 | _holdPlayer = NO; 130 | } 131 | } 132 | 133 | static BOOL _autoCreatePlayerWhenNeeded = YES; 134 | 135 | + (BOOL)autoCreatePlayerWhenNeeded { 136 | return _autoCreatePlayerWhenNeeded; 137 | } 138 | 139 | + (void)setAutoCreatePlayerWhenNeeded:(BOOL)autoCreatePlayerWhenNeeded { 140 | _autoCreatePlayerWhenNeeded = autoCreatePlayerWhenNeeded; 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /Library/Sources/SCAudioTools.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCAudioTools.m 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/8/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SCAudioTools.h" 12 | 13 | @implementation SCAudioTools { 14 | 15 | } 16 | 17 | #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE 18 | + (void)overrideCategoryMixWithOthers { 19 | 20 | UInt32 doSetProperty = 1; 21 | 22 | #pragma clang diagnostic push 23 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 24 | AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(doSetProperty), &doSetProperty); 25 | #pragma clang diagnostic pop 26 | } 27 | #endif 28 | 29 | + (void)mixAudio:(AVAsset*)audioAsset startTime:(CMTime)startTime withVideo:(NSURL*)inputUrl affineTransform:(CGAffineTransform)affineTransform toUrl:(NSURL*)outputUrl outputFileType:(NSString*)outputFileType withMaxDuration:(CMTime)maxDuration withCompletionBlock:(void(^)(NSError *))completionBlock { 30 | NSError * error = nil; 31 | AVMutableComposition * composition = [[AVMutableComposition alloc] init]; 32 | 33 | AVMutableCompositionTrack * videoTrackComposition = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; 34 | 35 | AVMutableCompositionTrack * audioTrackComposition = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; 36 | 37 | AVURLAsset * fileAsset = [AVURLAsset URLAssetWithURL:inputUrl options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]]; 38 | 39 | NSArray * videoTracks = [fileAsset tracksWithMediaType:AVMediaTypeVideo]; 40 | 41 | CMTime duration = ((AVAssetTrack*)[videoTracks objectAtIndex:0]).timeRange.duration; 42 | 43 | // We check if the recorded time if more than the limit 44 | if (CMTIME_COMPARE_INLINE(duration, >, maxDuration)) { 45 | duration = maxDuration; 46 | } 47 | 48 | for (AVAssetTrack * track in [audioAsset tracksWithMediaType:AVMediaTypeAudio]) { 49 | [audioTrackComposition insertTimeRange:CMTimeRangeMake(startTime, duration) ofTrack:track atTime:kCMTimeZero error:&error]; 50 | 51 | if (error != nil) { 52 | completionBlock(error); 53 | return; 54 | } 55 | } 56 | 57 | for (AVAssetTrack * track in videoTracks) { 58 | [videoTrackComposition insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:track atTime:kCMTimeZero error:&error]; 59 | 60 | if (error != nil) { 61 | completionBlock(error); 62 | return; 63 | } 64 | } 65 | 66 | videoTrackComposition.preferredTransform = affineTransform; 67 | 68 | AVAssetExportSession * exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough]; 69 | exportSession.outputFileType = outputFileType; 70 | exportSession.shouldOptimizeForNetworkUse = YES; 71 | exportSession.outputURL = outputUrl; 72 | 73 | [exportSession exportAsynchronouslyWithCompletionHandler:^ { 74 | NSError * error = nil; 75 | if (exportSession.error != nil) { 76 | NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithDictionary:exportSession.error.userInfo]; 77 | NSString * subLocalizedDescription = [userInfo objectForKey:NSLocalizedDescriptionKey]; 78 | [userInfo removeObjectForKey:NSLocalizedDescriptionKey]; 79 | [userInfo setObject:@"Failed to mix audio and video" forKey:NSLocalizedDescriptionKey]; 80 | [userInfo setObject:exportSession.outputFileType forKey:@"OutputFileType"]; 81 | [userInfo setObject:exportSession.outputURL forKey:@"OutputUrl"]; 82 | [userInfo setObject:subLocalizedDescription forKey:@"CauseLocalizedDescription"]; 83 | 84 | [userInfo setObject:[AVAssetExportSession allExportPresets] forKey:@"AllExportSessions"]; 85 | 86 | error = [NSError errorWithDomain:@"SCAudioVideoRecorder" code:500 userInfo:userInfo]; 87 | } 88 | 89 | completionBlock(error); 90 | }]; 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /Library/Sources/SCProcessingQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCProcessingQueue.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 02/07/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCProcessingQueue.h" 10 | #import "SCIOPixelBuffers.h" 11 | 12 | @interface SCProcessingQueue () { 13 | NSMutableArray *_queue; 14 | dispatch_semaphore_t _availableItemsToDequeue; 15 | dispatch_semaphore_t _availableItemsToEnqueue; 16 | dispatch_semaphore_t _accessQueue; 17 | BOOL _completed; 18 | } 19 | 20 | @end 21 | 22 | @implementation SCProcessingQueue 23 | 24 | - (instancetype)init { 25 | self = [super init]; 26 | 27 | if (self) { 28 | _queue = [NSMutableArray new]; 29 | _completed = NO; 30 | _maxQueueSize = 1; 31 | _availableItemsToDequeue = dispatch_semaphore_create(0); 32 | _accessQueue = dispatch_semaphore_create(1); 33 | self.maxQueueSize = 1; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | - (void)dealloc 40 | { 41 | [self stopProcessing]; 42 | } 43 | 44 | - (void)setMaxQueueSize:(NSUInteger)maxQueueSize { 45 | _availableItemsToEnqueue = dispatch_semaphore_create(0); 46 | 47 | for (NSUInteger i = 0; i < maxQueueSize; i++) { 48 | dispatch_semaphore_signal(_availableItemsToEnqueue); 49 | } 50 | 51 | _maxQueueSize = maxQueueSize; 52 | } 53 | 54 | - (void)_process:(id (^)())processingBlock { 55 | @autoreleasepool { 56 | while (!_completed) { 57 | BOOL shouldProcess = NO; 58 | 59 | @autoreleasepool { 60 | dispatch_semaphore_wait(_availableItemsToEnqueue, DISPATCH_TIME_FOREVER); 61 | shouldProcess = !_completed; 62 | 63 | BOOL shouldStopProcessing = NO; 64 | if (shouldProcess) { 65 | id data = processingBlock(); 66 | 67 | if (data != nil) { 68 | dispatch_semaphore_wait(_accessQueue, DISPATCH_TIME_FOREVER); 69 | [_queue addObject:data]; 70 | dispatch_semaphore_signal(_accessQueue); 71 | dispatch_semaphore_signal(_availableItemsToDequeue); 72 | } else { 73 | shouldStopProcessing = YES; 74 | dispatch_semaphore_signal(_availableItemsToEnqueue); 75 | } 76 | } else { 77 | dispatch_semaphore_signal(_availableItemsToEnqueue); 78 | } 79 | 80 | if (shouldStopProcessing) { 81 | [self stopProcessing]; 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | - (void)startProcessingWithBlock:(id (^)())processingBlock { 89 | [NSThread detachNewThreadSelector:@selector(_process:) toTarget:self withObject:processingBlock]; 90 | } 91 | 92 | - (void)stopProcessing { 93 | dispatch_semaphore_wait(_accessQueue, DISPATCH_TIME_FOREVER); 94 | 95 | _completed = YES; 96 | 97 | [_queue removeAllObjects]; 98 | 99 | while (dispatch_semaphore_signal(_availableItemsToDequeue) < 0) { 100 | 101 | } 102 | while (dispatch_semaphore_signal(_availableItemsToEnqueue) < 0) { 103 | 104 | } 105 | 106 | 107 | dispatch_semaphore_signal(_accessQueue); 108 | } 109 | 110 | - (id)dequeue { 111 | id obj = nil; 112 | 113 | if (!_completed) { 114 | dispatch_semaphore_wait(_availableItemsToDequeue, DISPATCH_TIME_FOREVER); 115 | 116 | dispatch_semaphore_wait(_accessQueue, DISPATCH_TIME_FOREVER); 117 | if (_queue.count > 0) { 118 | obj = _queue.firstObject; 119 | [_queue removeObjectAtIndex:0]; 120 | dispatch_semaphore_signal(_availableItemsToEnqueue); 121 | } else { 122 | dispatch_semaphore_signal(_availableItemsToDequeue); 123 | } 124 | 125 | dispatch_semaphore_signal(_accessQueue); 126 | } 127 | 128 | return obj; 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /Library/Sources/SCVideoConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoConfiguration.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 21/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCVideoConfiguration.h" 10 | 11 | @implementation SCVideoConfiguration 12 | 13 | - (id)init { 14 | self = [super init]; 15 | 16 | if (self) { 17 | self.bitrate = kSCVideoConfigurationDefaultBitrate; 18 | _size = CGSizeZero; 19 | _codec = kSCVideoConfigurationDefaultCodec; 20 | _scalingMode = kSCVideoConfigurationDefaultScalingMode; 21 | _affineTransform = CGAffineTransformIdentity; 22 | _timeScale = 1; 23 | _keepInputAffineTransform = YES; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | static CGSize MakeVideoSize(CGSize videoSize, float requestedWidth) { 30 | float ratio = videoSize.width / requestedWidth; 31 | 32 | if (ratio <= 1) { 33 | return videoSize; 34 | } 35 | 36 | return CGSizeMake(videoSize.width / ratio, videoSize.height / ratio); 37 | } 38 | 39 | - (NSDictionary *)createAssetWriterOptionsWithVideoSize:(CGSize)videoSize { 40 | NSDictionary *options = self.options; 41 | if (options != nil) { 42 | return options; 43 | } 44 | 45 | CGSize outputSize = self.size; 46 | unsigned long bitrate = (unsigned long)self.bitrate; 47 | 48 | if (self.preset != nil) { 49 | if ([self.preset isEqualToString:SCPresetLowQuality]) { 50 | bitrate = 500000; 51 | outputSize = MakeVideoSize(videoSize, 640); 52 | } else if ([self.preset isEqualToString:SCPresetMediumQuality]) { 53 | bitrate = 1000000; 54 | outputSize = MakeVideoSize(videoSize, 1280); 55 | } else if ([self.preset isEqualToString:SCPresetHighestQuality]) { 56 | bitrate = 6000000; 57 | outputSize = MakeVideoSize(videoSize, 1920); 58 | } else { 59 | NSLog(@"Unrecognized video preset %@", self.preset); 60 | } 61 | } 62 | 63 | if (CGSizeEqualToSize(outputSize, CGSizeZero)) { 64 | outputSize = videoSize; 65 | } 66 | 67 | if (self.sizeAsSquare) { 68 | if (videoSize.width > videoSize.height) { 69 | outputSize.width = videoSize.height; 70 | } else { 71 | outputSize.height = videoSize.width; 72 | } 73 | } 74 | 75 | NSMutableDictionary *compressionSettings = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:bitrate] forKey:AVVideoAverageBitRateKey]; 76 | 77 | if (self.shouldKeepOnlyKeyFrames) { 78 | [compressionSettings setObject:@1 forKey:AVVideoMaxKeyFrameIntervalKey]; 79 | } 80 | 81 | if (self.profileLevel) { 82 | [compressionSettings setObject:self.profileLevel forKey:AVVideoProfileLevelKey]; 83 | } 84 | // [compressionSettings setObject:@30 forKey:AVVideoAverageNonDroppableFrameRateKey]; 85 | [compressionSettings setObject:@NO forKey:AVVideoAllowFrameReorderingKey]; 86 | // [compressionSettings setObject:AVVideoH264EntropyModeCABAC forKey:AVVideoH264EntropyModeKey]; 87 | [compressionSettings setObject:@30 forKey:AVVideoExpectedSourceFrameRateKey]; 88 | 89 | return @{ 90 | AVVideoCodecKey : self.codec, 91 | AVVideoScalingModeKey : self.scalingMode, 92 | AVVideoWidthKey : [NSNumber numberWithInteger:outputSize.width], 93 | AVVideoHeightKey : [NSNumber numberWithInteger:outputSize.height], 94 | AVVideoCompressionPropertiesKey : compressionSettings 95 | }; 96 | 97 | } 98 | 99 | - (NSDictionary *)createAssetWriterOptionsUsingSampleBuffer:(CMSampleBufferRef)sampleBuffer { 100 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 101 | size_t width = CVPixelBufferGetWidth(imageBuffer); 102 | size_t height = CVPixelBufferGetHeight(imageBuffer); 103 | 104 | return [self createAssetWriterOptionsWithVideoSize:CGSizeMake(width, height)]; 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCSessionListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCSessionListViewController.m 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 14/08/14. 6 | // 7 | // 8 | 9 | #import "SCSessionListViewController.h" 10 | #import "SCRecordSessionManager.h" 11 | #import "SCSessionTableViewCell.h" 12 | 13 | @interface SCSessionListViewController () 14 | 15 | @end 16 | 17 | @implementation SCSessionListViewController 18 | 19 | 20 | - (void)viewDidLoad 21 | { 22 | [super viewDidLoad]; 23 | 24 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Save current" style:UIBarButtonItemStyleBordered target:self action:@selector(saveCurrentRecordSession)]; 25 | // Do any additional setup after loading the view. 26 | } 27 | 28 | - (void)saveCurrentRecordSession { 29 | [[SCRecordSessionManager sharedInstance] saveRecordSession:_recorder.session]; 30 | [self.tableView reloadData]; 31 | } 32 | 33 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 34 | if (_recorder.session.segments.count > 0) { 35 | [[SCRecordSessionManager sharedInstance] saveRecordSession:_recorder.session]; 36 | } 37 | NSDictionary *recordSessionMetadata = [[SCRecordSessionManager sharedInstance].savedRecordSessions objectAtIndex:indexPath.row]; 38 | 39 | SCRecordSession *newRecordSession = [SCRecordSession recordSession:recordSessionMetadata]; 40 | _recorder.session = newRecordSession; 41 | 42 | [self.navigationController popViewControllerAnimated:YES]; 43 | } 44 | 45 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 46 | SCSessionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Session"]; 47 | NSDictionary *recordSession = [[SCRecordSessionManager sharedInstance].savedRecordSessions objectAtIndex:indexPath.row]; 48 | 49 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 50 | [formatter setDateFormat:@"dd/MM/yyyy hh:mm"]; 51 | 52 | cell.dateLabel.text = [formatter stringFromDate:recordSession[SCRecordSessionDateKey]]; 53 | 54 | NSArray *recordSegments = recordSession[SCRecordSessionSegmentsKey]; 55 | 56 | cell.segmentsCountLabel.text = [NSString stringWithFormat:@"%d segments", (int)[recordSegments count]]; 57 | 58 | cell.durationLabel.text = [NSString stringWithFormat:@"%fs", [recordSession[SCRecordSessionDurationKey] doubleValue]]; 59 | 60 | if (recordSegments.count > 0) { 61 | NSDictionary *dictRepresentation = recordSegments.firstObject; 62 | NSString *directory = recordSession[SCRecordSessionDirectoryKey]; 63 | SCRecordSessionSegment *segment = [[SCRecordSessionSegment alloc] initWithDictionaryRepresentation:dictRepresentation directory:directory]; 64 | 65 | [cell.videoPlayerView.player setItemByAsset:segment.asset]; 66 | } 67 | 68 | return cell; 69 | } 70 | 71 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 72 | NSDictionary *recordSession = [[SCRecordSessionManager sharedInstance].savedRecordSessions objectAtIndex:indexPath.row]; 73 | 74 | NSArray *urls = recordSession[SCRecordSessionSegmentFilenamesKey]; 75 | NSFileManager *manager = [NSFileManager defaultManager]; 76 | 77 | for (NSString *path in urls) { 78 | [manager removeItemAtPath:path error:nil]; 79 | } 80 | 81 | [[SCRecordSessionManager sharedInstance] removeRecordSessionAtIndex:indexPath.row]; 82 | 83 | if ([_recorder.session.identifier isEqualToString:[recordSession objectForKey:SCRecordSessionIdentifierKey]]) { 84 | _recorder.session = nil; 85 | } 86 | 87 | [tableView beginUpdates]; 88 | 89 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; 90 | 91 | [tableView endUpdates]; 92 | } 93 | 94 | - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { 95 | return UITableViewCellEditingStyleDelete; 96 | } 97 | 98 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 99 | return [SCRecordSessionManager sharedInstance].savedRecordSessions.count; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Examples/ObjC/Sources/SCAudioRecordViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCAudioRecordViewController.m 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 18/12/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCAudioRecordViewController.h" 10 | 11 | @interface SCAudioRecordViewController () { 12 | SCRecorder *_recorder; 13 | SCRecordSession *_recordSession; 14 | } 15 | 16 | @property (strong, nonatomic) SCPlayer *player; 17 | 18 | @end 19 | 20 | @implementation SCAudioRecordViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | 26 | self.player = [SCPlayer player]; 27 | self.player.delegate = self; 28 | [self.player beginSendingPlayMessages]; 29 | 30 | _recorder = [SCRecorder recorder]; 31 | _recorder.delegate = self; 32 | _recorder.photoConfiguration.enabled = NO; 33 | _recorder.videoConfiguration.enabled = NO; 34 | 35 | NSError *error; 36 | if (![_recorder prepare:&error]) { 37 | [self showError:error]; 38 | } 39 | 40 | [self hidePlayControl:NO]; 41 | [self createSession]; 42 | } 43 | 44 | - (void)viewWillAppear:(BOOL)animated { 45 | [super viewWillAppear:animated]; 46 | 47 | [_recorder startRunning]; 48 | } 49 | 50 | - (void)viewDidDisappear:(BOOL)animated { 51 | [super viewDidDisappear:animated]; 52 | 53 | [_recorder stopRunning]; 54 | } 55 | 56 | - (void)dealloc { 57 | [self.player endSendingPlayMessages]; 58 | } 59 | 60 | - (void)showError:(NSError*)error { 61 | [[[UIAlertView alloc] initWithTitle:@"Something went wrong" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; 62 | } 63 | 64 | - (void)createSession { 65 | SCRecordSession *session = [SCRecordSession recordSession]; 66 | session.fileType = AVFileTypeAppleM4A; 67 | [self updateRecordTimeLabel:kCMTimeZero]; 68 | 69 | _recorder.session = session; 70 | } 71 | 72 | - (void)updateRecordTimeLabel:(CMTime)time { 73 | self.recordTimeLabel.text = [NSString stringWithFormat:@"%.2fs", CMTimeGetSeconds(time)]; 74 | } 75 | 76 | - (IBAction)recordPressed:(id)sender { 77 | if (_recorder.isRecording) { 78 | [_recorder pause]; 79 | } else { 80 | [_recorder record]; 81 | } 82 | 83 | self.recordButton.selected = _recorder.isRecording; 84 | } 85 | 86 | - (void)hidePlayControl:(BOOL)animated { 87 | [UIView animateWithDuration:animated ? 0.3 : 0 animations:^{ 88 | UIView *playView = self.playView; 89 | CGRect frame = playView.frame; 90 | frame.origin.y = self.view.frame.size.height; 91 | playView.frame = frame; 92 | }]; 93 | } 94 | 95 | - (void)showPlayControl:(BOOL)animated { 96 | [UIView animateWithDuration:animated ? 0.3 : 0 animations:^{ 97 | UIView *playView = self.playView; 98 | CGRect frame = playView.frame; 99 | frame.origin.y = self.view.frame.size.height - frame.size.height; 100 | playView.frame = frame; 101 | }]; 102 | } 103 | 104 | - (void)recorder:(SCRecorder *)recorder didAppendAudioSampleBuffer:(SCRecordSession *)recordSession { 105 | [self updateRecordTimeLabel:recordSession.duration]; 106 | } 107 | 108 | - (void)deleteRecordSession { 109 | [self.player setItemByAsset:nil]; 110 | [_recordSession removeAllSegments]; 111 | _recordSession = nil; 112 | } 113 | 114 | - (IBAction)stopRecordPressed:(id)sender { 115 | [_recorder pause:^{ 116 | [self deleteRecordSession]; 117 | [self showPlayControl:YES]; 118 | _recordSession = _recorder.session; 119 | 120 | AVAsset *asset = _recordSession.assetRepresentingSegments; 121 | self.playSlider.maximumValue = CMTimeGetSeconds(asset.duration); 122 | [self.player setItemByAsset:asset]; 123 | 124 | [self createSession]; 125 | }];} 126 | 127 | - (IBAction)playButtonPressed:(id)sender { 128 | if (self.player.isPlaying) { 129 | [self.player pause]; 130 | } else { 131 | [self.player play]; 132 | } 133 | 134 | [self _updatePlayButton]; 135 | } 136 | 137 | - (void)_updatePlayButton { 138 | self.playButton.selected = self.player.isPlaying; 139 | } 140 | 141 | - (void)player:(SCPlayer *)player didReachEndForItem:(AVPlayerItem *)item { 142 | [player pause]; 143 | [player seekToTime:kCMTimeZero]; 144 | [self _updatePlayButton]; 145 | } 146 | 147 | - (void)player:(SCPlayer *)player didPlay:(CMTime)currentTime loopsCount:(NSInteger)loopsCount { 148 | self.playSlider.value = CMTimeGetSeconds(currentTime); 149 | self.playLabel.text = [NSString stringWithFormat:@"%.2fs", CMTimeGetSeconds(currentTime)]; 150 | } 151 | 152 | - (IBAction)playSliderValueChanged:(id)sender { 153 | [self.player seekToTime:CMTimeMakeWithSeconds(self.playSlider.value, 1000)]; 154 | } 155 | 156 | - (IBAction)deletePressed:(id)sender { 157 | [self hidePlayControl:YES]; 158 | [self deleteRecordSession]; 159 | } 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderDelegate.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 18/03/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SCRecorder.h" 12 | 13 | typedef NS_ENUM(NSInteger, SCFlashMode) { 14 | SCFlashModeOff = AVCaptureFlashModeOff, 15 | SCFlashModeOn = AVCaptureFlashModeOn, 16 | SCFlashModeAuto = AVCaptureFlashModeAuto, 17 | SCFlashModeLight 18 | }; 19 | 20 | @class SCRecorder; 21 | 22 | @protocol SCRecorderDelegate 23 | 24 | @optional 25 | 26 | /** 27 | Called when the recorder has reconfigured the videoInput 28 | */ 29 | - (void)recorder:(SCRecorder *__nonnull)recorder didReconfigureVideoInput:(NSError *__nullable)videoInputError; 30 | 31 | /** 32 | Called when the recorder has reconfigured the audioInput 33 | */ 34 | - (void)recorder:(SCRecorder *__nonnull)recorder didReconfigureAudioInput:(NSError *__nullable)audioInputError; 35 | 36 | /** 37 | Called when the flashMode has changed 38 | */ 39 | - (void)recorder:(SCRecorder *__nonnull)recorder didChangeFlashMode:(SCFlashMode)flashMode error:(NSError *__nullable)error; 40 | 41 | /** 42 | Called when the capture session outputs a video sample buffer. 43 | This will be called in the SCRecorder internal queue, make sure 44 | you don't block the thread for too long. 45 | */ 46 | - (void)recorder:(SCRecorder *__nonnull)recorder didOutputVideoSampleBuffer:(__nonnull CMSampleBufferRef)videoSampleBuffer; 47 | 48 | /** 49 | Called when the capture session outputs an audio sample buffer. 50 | This will be called in the SCRecorder internal queue, make sure 51 | you don't block the thread for too long. 52 | */ 53 | - (void)recorder:(SCRecorder *__nonnull)recorder didOutputAudioSampleBuffer:(__nonnull CMSampleBufferRef)audioSampleBuffer; 54 | 55 | /** 56 | Called when the recorder has lost the focus. Returning true will make the recorder 57 | automatically refocus at the center. 58 | */ 59 | - (BOOL)recorderShouldAutomaticallyRefocus:(SCRecorder *__nonnull)recorder; 60 | 61 | /** 62 | Called before the recorder will start focusing 63 | */ 64 | - (void)recorderWillStartFocus:(SCRecorder *__nonnull)recorder; 65 | 66 | /** 67 | Called when the recorder has started focusing 68 | */ 69 | - (void)recorderDidStartFocus:(SCRecorder *__nonnull)recorder; 70 | 71 | /** 72 | Called when the recorder has finished focusing 73 | */ 74 | - (void)recorderDidEndFocus:(SCRecorder *__nonnull)recorder; 75 | 76 | /** 77 | Called before the recorder will start adjusting exposure 78 | */ 79 | - (void)recorderWillStartAdjustingExposure:(SCRecorder *__nonnull)recorder; 80 | 81 | /** 82 | Called when the recorder has started adjusting exposure 83 | */ 84 | - (void)recorderDidStartAdjustingExposure:(SCRecorder *__nonnull)recorder; 85 | 86 | /** 87 | Called when the recorder has finished adjusting exposure 88 | */ 89 | - (void)recorderDidEndAdjustingExposure:(SCRecorder *__nonnull)recorder; 90 | 91 | /** 92 | Called when the recorder has initialized the audio in a session 93 | */ 94 | - (void)recorder:(SCRecorder *__nonnull)recorder didInitializeAudioInSession:(SCRecordSession *__nonnull)session error:(NSError *__nullable)error; 95 | 96 | /** 97 | Called when the recorder has initialized the video in a session 98 | */ 99 | - (void)recorder:(SCRecorder *__nonnull)recorder didInitializeVideoInSession:(SCRecordSession *__nonnull)session error:(NSError *__nullable)error; 100 | 101 | /** 102 | Called when the recorder has started a segment in a session 103 | */ 104 | - (void)recorder:(SCRecorder *__nonnull)recorder didBeginSegmentInSession:(SCRecordSession *__nonnull)session error:(NSError *__nullable)error; 105 | 106 | /** 107 | Called when the recorder has completed a segment in a session 108 | */ 109 | - (void)recorder:(SCRecorder *__nonnull)recorder didCompleteSegment:(SCRecordSessionSegment *__nullable)segment inSession:(SCRecordSession *__nonnull)session error:(NSError *__nullable)error; 110 | 111 | /** 112 | Called when the recorder has appended a video buffer in a session 113 | */ 114 | - (void)recorder:(SCRecorder *__nonnull)recorder didAppendVideoSampleBufferInSession:(SCRecordSession *__nonnull)session; 115 | 116 | /** 117 | Called when the recorder has appended an audio buffer in a session 118 | */ 119 | - (void)recorder:(SCRecorder *__nonnull)recorder didAppendAudioSampleBufferInSession:(SCRecordSession *__nonnull)session; 120 | 121 | /** 122 | Called when the recorder has skipped an audio buffer in a session 123 | */ 124 | - (void)recorder:(SCRecorder *__nonnull)recorder didSkipAudioSampleBufferInSession:(SCRecordSession *__nonnull)session; 125 | 126 | /** 127 | Called when the recorder has skipped a video buffer in a session 128 | */ 129 | - (void)recorder:(SCRecorder *__nonnull)recorder didSkipVideoSampleBufferInSession:(SCRecordSession *__nonnull)session; 130 | 131 | /** 132 | Called when a session has reached the maxRecordDuration 133 | */ 134 | - (void)recorder:(SCRecorder *__nonnull)recorder didCompleteSession:(SCRecordSession *__nonnull)session; 135 | 136 | /** 137 | Gives an opportunity to the delegate to create an info dictionary for a record segment. 138 | */ 139 | - (NSDictionary *__nullable)createSegmentInfoForRecorder:(SCRecorder *__nonnull)recorder; 140 | 141 | @end 142 | -------------------------------------------------------------------------------- /Library/Sources/SCRecordSessionSegment.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecordSessionSegment.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 10/03/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCRecordSessionSegment.h" 10 | #import "SCRecordSession.h" 11 | 12 | @interface SCRecordSessionSegment() { 13 | AVAsset *_asset; 14 | __weak UIImage *_thumbnail; 15 | __weak UIImage *_lastImage; 16 | } 17 | 18 | @end 19 | 20 | @implementation SCRecordSessionSegment 21 | 22 | - (instancetype)initWithDictionaryRepresentation:(NSDictionary *)dictionary directory:(NSString *)directory { 23 | NSString *filename = dictionary[SCRecordSessionSegmentFilenameKey]; 24 | NSDictionary *info = dictionary[SCRecordSessionSegmentInfoKey]; 25 | 26 | if (filename != nil) { 27 | NSURL *url = [SCRecordSessionSegment segmentURLForFilename:filename andDirectory:directory]; 28 | return [self initWithURL:url info:info]; 29 | } 30 | 31 | return nil; 32 | } 33 | 34 | - (instancetype)initWithURL:(NSURL *)url info:(NSDictionary *)info { 35 | self = [self init]; 36 | 37 | if (self) { 38 | _url = url; 39 | _info = info; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | - (void)deleteFile { 46 | NSError *error = nil; 47 | [[NSFileManager defaultManager] removeItemAtURL:_url error:&error]; 48 | _url = nil; 49 | _asset = nil; 50 | } 51 | 52 | - (AVAsset *)asset { 53 | if (_asset == nil) { 54 | _asset = [AVAsset assetWithURL:_url]; 55 | } 56 | 57 | return _asset; 58 | } 59 | 60 | - (CMTime)duration { 61 | return [self asset].duration; 62 | } 63 | 64 | - (UIImage *)thumbnail { 65 | UIImage *image = _thumbnail; 66 | if (image == nil) { 67 | AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:self.asset]; 68 | imageGenerator.appliesPreferredTrackTransform = YES; 69 | 70 | NSError *error = nil; 71 | CGImageRef thumbnailImage = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:nil error:&error]; 72 | 73 | if (error == nil) { 74 | image = [UIImage imageWithCGImage:thumbnailImage]; 75 | _thumbnail = image; 76 | } else { 77 | NSLog(@"Unable to generate thumbnail for %@: %@", self.url, error.localizedDescription); 78 | } 79 | } 80 | 81 | return image; 82 | } 83 | 84 | - (UIImage *)lastImage { 85 | UIImage *image = _lastImage; 86 | if (image == nil) { 87 | AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:self.asset]; 88 | imageGenerator.appliesPreferredTrackTransform = YES; 89 | 90 | NSError *error = nil; 91 | CGImageRef lastImage = [imageGenerator copyCGImageAtTime:self.duration actualTime:nil error:&error]; 92 | 93 | if (error == nil) { 94 | image = [UIImage imageWithCGImage:lastImage]; 95 | _lastImage = image; 96 | } else { 97 | NSLog(@"Unable to generate lastImage for %@: %@", self.url, error.localizedDescription); 98 | } 99 | } 100 | 101 | return image; 102 | } 103 | 104 | - (float)frameRate { 105 | NSArray *tracks = [self.asset tracksWithMediaType:AVMediaTypeVideo]; 106 | 107 | if (tracks.count == 0) { 108 | return 0; 109 | } 110 | 111 | AVAssetTrack *videoTrack = [tracks firstObject]; 112 | 113 | return videoTrack.nominalFrameRate; 114 | } 115 | 116 | - (void)setUrl:(NSURL *)url { 117 | _url = url; 118 | _asset = nil; 119 | } 120 | 121 | - (NSDictionary *)dictionaryRepresentation { 122 | if (self.info == nil) { 123 | return @{ SCRecordSessionSegmentFilenameKey : self.url.lastPathComponent }; 124 | } else { 125 | return @{ 126 | SCRecordSessionSegmentFilenameKey : self.url.lastPathComponent, 127 | SCRecordSessionSegmentInfoKey : self.info 128 | }; 129 | } 130 | } 131 | 132 | - (BOOL)fileUrlExists { 133 | return [[NSFileManager defaultManager] fileExistsAtPath:self.url.path]; 134 | } 135 | 136 | + (NSURL *)segmentURLForFilename:(NSString *)filename andDirectory:(NSString *)directory { 137 | NSURL *directoryUrl = nil; 138 | 139 | if ([SCRecordSessionTemporaryDirectory isEqualToString:directory]) { 140 | directoryUrl = [NSURL fileURLWithPath:NSTemporaryDirectory()]; 141 | } else if ([SCRecordSessionCacheDirectory isEqualToString:directory]) { 142 | NSArray *myPathList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 143 | directoryUrl = [NSURL fileURLWithPath:myPathList.firstObject]; 144 | } else if ([SCRecordSessionDocumentDirectory isEqualToString:directory]) { 145 | NSArray *myPathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 146 | directoryUrl = [NSURL fileURLWithPath:myPathList.firstObject]; 147 | } else { 148 | directoryUrl = [NSURL fileURLWithPath:directory]; 149 | } 150 | 151 | return [directoryUrl URLByAppendingPathComponent:filename]; 152 | } 153 | 154 | + (SCRecordSessionSegment *)segmentWithURL:(NSURL *)url info:(NSDictionary *)info { 155 | return [[SCRecordSessionSegment alloc] initWithURL:url info:info]; 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /Library/Sources/SCPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoPlayer.h 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 8/30/13. 6 | // Copyright (c) 2013 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SCImageView.h" 12 | 13 | @class SCPlayer; 14 | 15 | @protocol SCPlayerDelegate 16 | 17 | @optional 18 | 19 | /** 20 | Called when the player has played some frames. The loopsCount will contains the number of 21 | loop if the curent item was set using setSmoothItem. 22 | */ 23 | - (void)player:(SCPlayer *__nonnull)player didPlay:(CMTime)currentTime loopsCount:(NSInteger)loopsCount; 24 | 25 | /** 26 | Called when the item has been changed on the SCPlayer 27 | */ 28 | - (void)player:(SCPlayer *__nonnull)player didChangeItem:(AVPlayerItem *__nullable)item; 29 | 30 | /** 31 | Called when the item has reached end 32 | */ 33 | - (void)player:(SCPlayer *__nonnull)player didReachEndForItem:(AVPlayerItem *__nonnull)item; 34 | 35 | /** 36 | Called when the item is ready to play 37 | */ 38 | - (void)player:(SCPlayer *__nonnull)player itemReadyToPlay:(AVPlayerItem *__nonnull)item; 39 | 40 | /** 41 | Called when the player has setup the renderer so it can receive the image in the 42 | proper orientation. 43 | */ 44 | - (void)player:(SCPlayer *__nonnull)player didSetupSCImageView:(SCImageView *__nonnull)SCImageView; 45 | 46 | /** 47 | Called when the item has updated the time ranges that have been loaded 48 | */ 49 | - (void)player:(SCPlayer *__nonnull)player didUpdateLoadedTimeRanges:(CMTimeRange)timeRange; 50 | 51 | 52 | /** 53 | Called when the item playback buffer is empty 54 | */ 55 | - (void)player:(SCPlayer *__nonnull)player itemPlaybackBufferIsEmpty:(AVPlayerItem *__nullable)item; 56 | 57 | 58 | @end 59 | 60 | /** 61 | A player that inherits from the standard AVPlayer but adds some feature, such as the 62 | CIImageRenderer support. 63 | */ 64 | @interface SCPlayer : AVPlayer 65 | 66 | /** 67 | The delegate that will receive the messages 68 | */ 69 | @property (weak, nonatomic) __nullable id delegate; 70 | 71 | /** 72 | Whether the video should start again from the beginning when its reaches the end 73 | */ 74 | @property (assign, nonatomic) BOOL loopEnabled; 75 | 76 | /** 77 | Will be true if beginSendingPlayMessages has been called. 78 | */ 79 | @property (readonly, nonatomic) BOOL isSendingPlayMessages; 80 | 81 | /** 82 | Whether this instance is currently playing. 83 | */ 84 | @property (readonly, nonatomic) BOOL isPlaying; 85 | 86 | /** 87 | Whether this instance displays default rendered video 88 | */ 89 | @property (assign, nonatomic) BOOL shouldSuppressPlayerRendering; 90 | 91 | /** 92 | The actual item duration. 93 | */ 94 | @property (readonly, nonatomic) CMTime itemDuration; 95 | 96 | /** 97 | The total currently loaded and playable time. 98 | */ 99 | @property (readonly, nonatomic) CMTime playableDuration; 100 | 101 | /** 102 | If true, the player will figure out an affine transform so the video best fits the screen. The resulting video may not be in the correct device orientation though. 103 | For example, if the video is in landscape and the current device orientation is in portrait mode, 104 | with this property enabled the video will be rotated so it fits the entire screen. This avoid 105 | showing the black border on the sides. If your app supports multiple orientation, you typically 106 | wouldn't want this feature on. 107 | */ 108 | @property (assign, nonatomic) BOOL autoRotate; 109 | 110 | /** 111 | The renderer for the CIImage. If this property is set, the player will set the CIImage 112 | property when the current frame changes. 113 | */ 114 | @property (strong, nonatomic) SCImageView *__nullable SCImageView; 115 | 116 | /** 117 | Convenient method to return a new instance of a SCPlayer 118 | */ 119 | + (SCPlayer *__nonnull)player; 120 | 121 | /** 122 | Ask the SCPlayer to send didPlay messages during the playback 123 | endSendingPlayMessages must be called, otherwise the SCPlayer will never 124 | be deallocated 125 | */ 126 | - (void)beginSendingPlayMessages; 127 | 128 | /** 129 | Ask the SCPlayer to stop sending didPlay messages during the playback 130 | */ 131 | - (void)endSendingPlayMessages; 132 | 133 | /** 134 | Set the item using a file string path. 135 | */ 136 | - (void)setItemByStringPath:(NSString *__nullable)stringPath; 137 | 138 | /** 139 | Set the item using an URL 140 | */ 141 | - (void)setItemByUrl:(NSURL *__nullable)url; 142 | 143 | /** 144 | Set the item using an Asset 145 | */ 146 | - (void)setItemByAsset:(AVAsset *__nullable)asset; 147 | 148 | /** 149 | Set the item using an AVPlayerItem 150 | */ 151 | - (void)setItem:(AVPlayerItem *__nullable)item; 152 | 153 | /** 154 | Set an item using a file string path. This will generate an AVComposition containing "loopCount" 155 | times the item. This avoids the hiccup when looping for up to "loopCount" times. 156 | */ 157 | - (void)setSmoothLoopItemByStringPath:(NSString *__nullable)stringPath smoothLoopCount:(NSUInteger)loopCount; 158 | 159 | /** 160 | Set the item using an URL. This will generate an AVComposition containing "loopCount" 161 | times the item. This avoids the hiccup when looping for up to "loopCount" times. 162 | */ 163 | - (void)setSmoothLoopItemByUrl:(NSURL *__nullable)url smoothLoopCount:(NSUInteger)loopCount; 164 | 165 | /** 166 | Set the item using an Asset. This will generate an AVComposition containing "loopCount" 167 | times the item. This avoids the hiccup when looping for up to "loopCount" times. 168 | */ 169 | - (void)setSmoothLoopItemByAsset:(AVAsset *__nullable)asset smoothLoopCount:(NSUInteger)loopCount; 170 | 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /Library/Sources/SCContext.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCContext.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 28/05/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCContext.h" 10 | 11 | @implementation SCContext 12 | 13 | NSString *__nonnull const SCContextOptionsCGContextKey = @"CGContext"; 14 | NSString *__nonnull const SCContextOptionsEAGLContextKey = @"EAGLContext"; 15 | NSString *__nonnull const SCContextOptionsMTLDeviceKey = @"MTLDevice"; 16 | 17 | static NSDictionary *SCContextCreateCIContextOptions() { 18 | return @{kCIContextWorkingColorSpace : [NSNull null], kCIContextOutputColorSpace : [NSNull null]}; 19 | } 20 | 21 | - (instancetype)initWithSoftwareRenderer:(BOOL)softwareRenderer { 22 | self = [super init]; 23 | 24 | if (self) { 25 | NSMutableDictionary *options = SCContextCreateCIContextOptions().mutableCopy; 26 | options[kCIContextUseSoftwareRenderer] = @(softwareRenderer); 27 | _CIContext = [CIContext contextWithOptions:options]; 28 | if (softwareRenderer) { 29 | _type = SCContextTypeCPU; 30 | } else { 31 | _type = SCContextTypeDefault; 32 | } 33 | } 34 | 35 | return self; 36 | } 37 | 38 | - (instancetype)initWithCGContextRef:(CGContextRef)contextRef { 39 | self = [super init]; 40 | 41 | if (self) { 42 | _CIContext = [CIContext contextWithCGContext:contextRef options:SCContextCreateCIContextOptions()]; 43 | _type = SCContextTypeCoreGraphics; 44 | } 45 | 46 | return self; 47 | } 48 | 49 | - (instancetype)initWithEAGLContext:(EAGLContext *)context { 50 | self = [super init]; 51 | 52 | if (self) { 53 | _EAGLContext = context; 54 | 55 | _CIContext = [CIContext contextWithEAGLContext:_EAGLContext options:SCContextCreateCIContextOptions()]; 56 | _type = SCContextTypeEAGL; 57 | } 58 | 59 | return self; 60 | } 61 | 62 | - (instancetype)initWithMTLDevice:(id)device { 63 | self = [super init]; 64 | 65 | if (self) { 66 | _MTLDevice = device; 67 | 68 | _CIContext = [CIContext contextWithMTLDevice:device options:SCContextCreateCIContextOptions()]; 69 | _type = SCContextTypeMetal; 70 | } 71 | 72 | return self; 73 | } 74 | 75 | + (BOOL)supportsType:(SCContextType)contextType { 76 | id CIContextClass = [CIContext class]; 77 | 78 | switch (contextType) { 79 | case SCContextTypeMetal: 80 | return [CIContextClass respondsToSelector:@selector(contextWithMTLDevice:options:)]; 81 | case SCContextTypeCoreGraphics: 82 | return [CIContextClass respondsToSelector:@selector(contextWithCGContext:options:)]; 83 | case SCContextTypeEAGL: 84 | return [CIContextClass respondsToSelector:@selector(contextWithEAGLContext:options:)]; 85 | case SCContextTypeAuto: 86 | case SCContextTypeDefault: 87 | case SCContextTypeCPU: 88 | return YES; 89 | } 90 | return NO; 91 | } 92 | 93 | + (SCContextType)suggestedContextType { 94 | // On iOS 9.0, Metal does not behave nicely with gaussian blur filters 95 | // if ([SCContext supportsType:SCContextTypeMetal]) { 96 | // return SCContextTypeMetal; 97 | // } else 98 | if ([SCContext supportsType:SCContextTypeEAGL]) { 99 | return SCContextTypeEAGL; 100 | } else if ([SCContext supportsType:SCContextTypeCoreGraphics]) { 101 | return SCContextTypeCoreGraphics; 102 | } else { 103 | return SCContextTypeDefault; 104 | } 105 | } 106 | 107 | + (SCContext *)contextWithType:(SCContextType)contextType options:(NSDictionary *)options { 108 | switch (contextType) { 109 | case SCContextTypeAuto: 110 | return [SCContext contextWithType:[SCContext suggestedContextType] options:options]; 111 | case SCContextTypeMetal: { 112 | id device = options[SCContextOptionsMTLDeviceKey]; 113 | if (device == nil) { 114 | device = MTLCreateSystemDefaultDevice(); 115 | } 116 | 117 | return [[SCContext alloc] initWithMTLDevice:device]; 118 | } 119 | case SCContextTypeCoreGraphics: { 120 | CGContextRef context = (__bridge CGContextRef)(options[SCContextOptionsCGContextKey]); 121 | 122 | if (context == nil) { 123 | [NSException raise:@"MissingCGContext" format:@"SCContextTypeCoreGraphics needs to have a CGContext attached to the SCContextOptionsCGContextKey in the options"]; 124 | } 125 | 126 | return [[SCContext alloc] initWithCGContextRef:context]; 127 | } 128 | case SCContextTypeCPU: 129 | return [[SCContext alloc] initWithSoftwareRenderer:YES]; 130 | case SCContextTypeDefault: 131 | return [[SCContext alloc] initWithSoftwareRenderer:NO]; 132 | case SCContextTypeEAGL: { 133 | EAGLContext *context = options[SCContextOptionsEAGLContextKey]; 134 | 135 | if (context == nil) { 136 | static dispatch_once_t onceToken; 137 | static EAGLSharegroup *shareGroup; 138 | dispatch_once(&onceToken, ^{ 139 | shareGroup = [EAGLSharegroup new]; 140 | }); 141 | 142 | context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:shareGroup]; 143 | } 144 | 145 | return [[SCContext alloc] initWithEAGLContext:context]; 146 | } 147 | default: 148 | [NSException raise:@"InvalidContextType" format:@"Invalid context type %d", (int)contextType]; 149 | break; 150 | } 151 | 152 | return nil; 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /Library/Sources/SCVideoConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCVideoConfiguration.h 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 21/11/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SCMediaTypeConfiguration.h" 12 | #import "SCFilter.h" 13 | 14 | #define kSCVideoConfigurationDefaultCodec AVVideoCodecH264 15 | #define kSCVideoConfigurationDefaultScalingMode AVVideoScalingModeResizeAspectFill 16 | #define kSCVideoConfigurationDefaultBitrate 2000000 17 | 18 | typedef enum : NSUInteger { 19 | SCWatermarkAnchorLocationTopLeft, 20 | SCWatermarkAnchorLocationTopRight, 21 | SCWatermarkAnchorLocationBottomLeft, 22 | SCWatermarkAnchorLocationBottomRight 23 | } SCWatermarkAnchorLocation; 24 | 25 | @protocol SCVideoOverlay 26 | 27 | @optional 28 | 29 | /** 30 | Called to determine whether setFrame:, updateWithVideoTime: and layoutIfNeeded should be called on the main thread. 31 | You should avoid returning YES as much as possible from this method, since it will potentially 32 | greatly reduce the encoding speed. Some views like UITextView requires to layout on the main thread. 33 | */ 34 | - (BOOL)requiresUpdateOnMainThreadAtVideoTime:(NSTimeInterval)time videoSize:(CGSize)videoSize; 35 | 36 | /** 37 | Update the underlying view with the given time. 38 | This method will be called on the main thread if requiresVideoTimeUpdateOnMainThread returns true, 39 | otherwise it will be called in an arbitrary queue managed by the SCAssetExportSession. 40 | */ 41 | - (void)updateWithVideoTime:(NSTimeInterval)time; 42 | 43 | @end 44 | 45 | @interface SCVideoConfiguration : SCMediaTypeConfiguration 46 | 47 | /** 48 | Change the size of the video 49 | If options has been changed, this property will be ignored 50 | If this value is CGSizeZero, the input video size received 51 | from the camera will be used 52 | Default is CGSizeZero 53 | */ 54 | @property (assign, nonatomic) CGSize size; 55 | 56 | /** 57 | Change the affine transform for the video 58 | If options has been changed, this property will be ignored 59 | */ 60 | @property (assign, nonatomic) CGAffineTransform affineTransform; 61 | 62 | /** 63 | Set the codec used for the video 64 | Default is AVVideoCodecH264 65 | */ 66 | @property (copy, nonatomic) NSString *__nonnull codec; 67 | 68 | /** 69 | Set the video scaling mode 70 | */ 71 | @property (copy, nonatomic) NSString *__nonnull scalingMode; 72 | 73 | /** 74 | The maximum framerate that this SCRecordSession should handle 75 | If the camera appends too much frames, they will be dropped. 76 | If this property's value is 0, it will use the current video 77 | framerate from the camera. 78 | */ 79 | @property (assign, nonatomic) CMTimeScale maxFrameRate; 80 | 81 | /** 82 | The time scale of the video 83 | A value more than 1 will make the buffers last longer, it creates 84 | a slow motion effect. A value less than 1 will make the buffers be 85 | shorter, it creates a timelapse effect. 86 | 87 | Only used in SCRecorder. 88 | */ 89 | @property (assign, nonatomic) CGFloat timeScale; 90 | 91 | /** 92 | If true and videoSize is CGSizeZero, the videoSize 93 | used will equal to the minimum width or height found, 94 | thus making the video square. 95 | */ 96 | @property (assign, nonatomic) BOOL sizeAsSquare; 97 | 98 | /** 99 | If true, each frame will be encoded as a keyframe 100 | This is needed if you want to merge the recordSegments using 101 | the passthrough preset. This will seriously impact the video 102 | size. You can set this to NO and change the recordSegmentsMergePreset if you want 103 | a better quality/size ratio, but the merge will be slower. 104 | Default is NO 105 | */ 106 | @property (assign, nonatomic) BOOL shouldKeepOnlyKeyFrames; 107 | 108 | /** 109 | If not nil, each appended frame will be processed by this SCFilter. 110 | While it seems convenient, this removes the possibility to change the 111 | filter after the segment has been added. 112 | Setting a new filter will cause the SCRecordSession to stop the 113 | current record segment if the previous filter was NIL and the 114 | new filter is NOT NIL or vice versa. If you want to have a smooth 115 | transition between filters in the same record segment, make sure to set 116 | an empty SCFilterGroup instead of setting this property to nil. 117 | */ 118 | @property (strong, nonatomic) SCFilter *__nullable filter; 119 | 120 | /** 121 | If YES, the affineTransform will be ignored and the output affineTransform 122 | will be the same as the input asset. 123 | 124 | Only used in SCAssetExportSession. 125 | */ 126 | @property (assign, nonatomic) BOOL keepInputAffineTransform; 127 | 128 | /** 129 | The video composition to use. 130 | 131 | Only used in SCAssetExportSession. 132 | */ 133 | @property (strong, nonatomic) AVVideoComposition *__nullable composition; 134 | 135 | /** 136 | The watermark to use. If the composition is not set, this watermark 137 | image will be applied on the exported video. 138 | 139 | Only used in SCAssetExportSession. 140 | */ 141 | @property (strong, nonatomic) UIImage *__nullable watermarkImage; 142 | 143 | /** 144 | The watermark image location and size in the input video frame coordinates. 145 | 146 | Only used in SCAssetExportSession. 147 | */ 148 | @property (assign, nonatomic) CGRect watermarkFrame; 149 | 150 | /** 151 | Specify a buffer size to use. By default the SCAssetExportSession tries 152 | to figure out which size to use by looking at the composition and the natural 153 | size of the inputAsset. If the filter you set return back an image with a different 154 | size, you should put the output size here. 155 | 156 | Only used in SCAssetExportSession. 157 | Default is CGSizeZero 158 | */ 159 | @property (assign, nonatomic) CGSize bufferSize; 160 | 161 | /** 162 | Set a specific key to the video profile 163 | */ 164 | @property (assign, nonatomic) NSString *__nullable profileLevel; 165 | 166 | /** 167 | The overlay view that will be drawn on top of the video. 168 | 169 | Only used in SCAssetExportSession. 170 | */ 171 | @property (strong, nonatomic) UIView *__nullable overlay; 172 | 173 | /** 174 | The watermark anchor location. 175 | 176 | Default is top left 177 | 178 | Only used in SCAssetExportSession. 179 | */ 180 | @property (assign, nonatomic) SCWatermarkAnchorLocation watermarkAnchorLocation; 181 | 182 | 183 | - (NSDictionary *__nonnull)createAssetWriterOptionsWithVideoSize:(CGSize)videoSize; 184 | 185 | @end 186 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderFocusTargetView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XHCameraTagetView.m 3 | // iyilunba 4 | // 5 | // Created by 曾 宪华 on 13-11-8. 6 | // Copyright (c) 2013年 曾 宪华 开发团队(http://iyilunba.com ). All rights reserved. 7 | // 8 | 9 | #import "SCRecorderFocusTargetView.h" 10 | 11 | #define kInsideCircleAnimationKey @"insideCircleAnimationKey" 12 | #define kOutsideCircleAnimationKey @"outsideCircleAnimationKey" 13 | 14 | #define kRemoveCircleAnimationKey @"removeCircleAnimationKey" 15 | 16 | @interface SCRecorderFocusTargetView () 17 | 18 | @property (nonatomic, strong) UIImageView *outsideCircle; 19 | @property (nonatomic, strong) UIImageView *insideCircle; 20 | 21 | @end 22 | 23 | @implementation SCRecorderFocusTargetView 24 | 25 | - (id)initWithFrame:(CGRect)frame 26 | { 27 | self = [super initWithFrame:frame]; 28 | if (self) { 29 | [self setup]; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)setup { 35 | self.insideFocusTargetImageSizeRatio = 0.5; 36 | CGRect bounds = self.bounds; 37 | CGPoint center = CGPointMake(CGRectGetWidth(bounds) / 2.0, CGRectGetHeight(bounds) / 2.0); 38 | 39 | CGRect insideCircleFrame = bounds; 40 | insideCircleFrame.size.width = insideCircleFrame.size.height = insideCircleFrame.size.width - 25; 41 | UIImageView *insideCircle = [[UIImageView alloc] initWithFrame:insideCircleFrame]; 42 | insideCircle.image = nil; 43 | insideCircle.center = center; 44 | self.insideCircle = insideCircle; 45 | 46 | CGRect outsideCircleFrame = bounds; 47 | outsideCircleFrame.size.width = outsideCircleFrame.size.height = outsideCircleFrame.size.width; 48 | UIImageView *outsideCircle = [[UIImageView alloc] initWithFrame:outsideCircleFrame]; 49 | outsideCircle.image = nil; 50 | outsideCircle.center = center; 51 | self.outsideCircle = outsideCircle; 52 | 53 | [self addSubview:self.outsideCircle]; 54 | [self addSubview:self.insideCircle]; 55 | } 56 | 57 | - (void)startTargeting { 58 | // 判断是否已经add了这个animation 59 | if ([self.insideCircle.layer.animationKeys containsObject:kInsideCircleAnimationKey] && [self.outsideCircle.layer.animationKeys containsObject:kOutsideCircleAnimationKey]) { 60 | return; 61 | } 62 | 63 | if ([self.insideCircle.layer.animationKeys containsObject:kRemoveCircleAnimationKey] && [self.outsideCircle.layer.animationKeys containsObject:kRemoveCircleAnimationKey]) { 64 | [self.insideCircle.layer removeAnimationForKey:kRemoveCircleAnimationKey]; 65 | [self.outsideCircle.layer removeAnimationForKey:kRemoveCircleAnimationKey]; 66 | } 67 | 68 | // insideCircle 微微的闪烁 69 | CABasicAnimation *insideCircleAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 70 | insideCircleAnimation.beginTime = CACurrentMediaTime() + 0.1; 71 | insideCircleAnimation.duration = 0.5; 72 | insideCircleAnimation.fromValue = [NSNumber numberWithFloat:1.0f]; 73 | insideCircleAnimation.toValue = [NSNumber numberWithFloat:0.5f]; 74 | insideCircleAnimation.repeatCount = HUGE_VAL; 75 | insideCircleAnimation.autoreverses = YES; 76 | insideCircleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 77 | 78 | 79 | // outsideCircle 匀速的缩放 80 | CABasicAnimation *outsideCircleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 81 | outsideCircleAnimation.beginTime = CACurrentMediaTime() + 0.1; 82 | outsideCircleAnimation.fromValue = [NSNumber numberWithFloat:1.0f]; 83 | outsideCircleAnimation.toValue = [NSNumber numberWithFloat:0.85f]; 84 | outsideCircleAnimation.repeatCount = HUGE_VAL; 85 | outsideCircleAnimation.autoreverses = YES; 86 | outsideCircleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 87 | 88 | [self.insideCircle.layer addAnimation:insideCircleAnimation forKey:kInsideCircleAnimationKey]; 89 | [self.outsideCircle.layer addAnimation:outsideCircleAnimation forKey:kOutsideCircleAnimationKey]; 90 | } 91 | 92 | - (void)stopTargeting { 93 | if (!([self.insideCircle.layer.animationKeys containsObject:kInsideCircleAnimationKey] && [self.outsideCircle.layer.animationKeys containsObject:kOutsideCircleAnimationKey])) { 94 | return; 95 | } 96 | 97 | [self.insideCircle.layer removeAnimationForKey:kInsideCircleAnimationKey]; 98 | [self.outsideCircle.layer removeAnimationForKey:kOutsideCircleAnimationKey]; 99 | 100 | CABasicAnimation *scaleAniamtion = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 101 | scaleAniamtion.fromValue = [NSNumber numberWithFloat:1.0f]; 102 | scaleAniamtion.toValue = [NSNumber numberWithFloat:.0f]; 103 | 104 | CABasicAnimation *fadeAnim=[CABasicAnimation animationWithKeyPath:@"opacity"]; 105 | fadeAnim.fromValue=[NSNumber numberWithDouble:1.0]; 106 | fadeAnim.toValue=[NSNumber numberWithDouble:0.0]; 107 | 108 | CAAnimationGroup *group = [CAAnimationGroup animation]; 109 | group.beginTime = CACurrentMediaTime() + 0.3; 110 | group.fillMode = kCAFillModeForwards; 111 | group.removedOnCompletion = NO; 112 | group.duration = 0.3; 113 | group.repeatCount = 1; 114 | group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 115 | group.animations = [NSArray arrayWithObjects:scaleAniamtion, fadeAnim, nil]; 116 | 117 | [self.insideCircle.layer addAnimation:group forKey:kRemoveCircleAnimationKey]; 118 | [self.outsideCircle.layer addAnimation:group forKey:kRemoveCircleAnimationKey]; 119 | } 120 | 121 | - (void)layoutSubviews { 122 | [super layoutSubviews]; 123 | 124 | CGRect frame = self.bounds; 125 | self.outsideCircle.frame = frame; 126 | 127 | float width = self.bounds.size.width; 128 | float height = self.bounds.size.height; 129 | 130 | frame.size.width = width * self.insideFocusTargetImageSizeRatio; 131 | frame.size.height = height * self.insideFocusTargetImageSizeRatio; 132 | frame.origin.x = width / 2 - frame.size.width / 2; 133 | frame.origin.y = height / 2 - frame.size.height / 2; 134 | 135 | self.insideCircle.frame = frame; 136 | } 137 | 138 | - (UIImage*)insideFocusTargetImage 139 | { 140 | return self.insideCircle.image; 141 | } 142 | 143 | - (void)setInsideFocusTargetImage:(UIImage *)insideFocusTargetImage 144 | { 145 | self.insideCircle.image = insideFocusTargetImage; 146 | } 147 | 148 | - (UIImage*)outsideFocusTargetImage 149 | { 150 | return self.outsideCircle.image; 151 | } 152 | 153 | - (void)setOutsideFocusTargetImage:(UIImage *)outsideFocusTargetImage 154 | { 155 | self.outsideCircle.image = outsideFocusTargetImage; 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /Library/Sources/SCFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilter.h 3 | // CoreImageShop 4 | // 5 | // Created by Simon CORSIN on 16/05/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE 12 | #import 13 | #else 14 | #import 15 | #endif 16 | 17 | #import "SCFilterAnimation.h" 18 | 19 | @class SCFilter; 20 | @protocol SCFilterDelegate 21 | 22 | /** 23 | Called when a parameter changed from the SCFilter instance. 24 | */ 25 | - (void)filter:(SCFilter *__nonnull)filter didChangeParameter:(NSString *__nonnull)parameterKey; 26 | 27 | /** 28 | Called before the filter start processing an image. 29 | */ 30 | - (void)filter:(SCFilter *__nonnull)filter willProcessImage:(CIImage *__nullable)image atTime:(CFTimeInterval)time; 31 | 32 | /** 33 | Called when the parameter values has been reset to defaults. 34 | */ 35 | - (void)filterDidResetToDefaults:(SCFilter *__nonnull)filter; 36 | 37 | @end 38 | 39 | @interface SCFilter : NSObject 40 | 41 | /** 42 | The underlying CIFilter attached to this SCFilter instance. 43 | */ 44 | @property (readonly, nonatomic) CIFilter *__nullable CIFilter; 45 | 46 | /** 47 | The name of this filter. By default it takes the name of the attached 48 | CIFilter. 49 | */ 50 | @property (strong, nonatomic) NSString *__nullable name; 51 | 52 | /** 53 | Whether this filter should process the images from imageByProcessingImage:. 54 | */ 55 | @property (assign, nonatomic) BOOL enabled; 56 | 57 | /** 58 | Whether this SCFilter and all its subfilters have no CIFilter attached. 59 | If YES, it means that calling imageByProcessingImage: will always return the input 60 | image without any modification. 61 | */ 62 | @property (readonly, nonatomic) BOOL isEmpty; 63 | 64 | /** 65 | Contains every added sub filters. 66 | */ 67 | @property (readonly, nonatomic) NSArray *__nonnull subFilters; 68 | 69 | /** 70 | Contains every added SCFilterAnimations 71 | */ 72 | @property (readonly, nonatomic) NSArray *__nonnull animations; 73 | 74 | /** 75 | Set a delegate that will receive messages when some parameters change 76 | */ 77 | @property (weak, nonatomic) __nullable id delegate; 78 | 79 | /** 80 | Initialize a SCFilter with an attached CIFilter. 81 | CIFilter can be nil. 82 | */ 83 | - (nullable instancetype)initWithCIFilter:(CIFilter *__nullable)filter; 84 | 85 | /** 86 | Returns the attached CIFilter parameter value for the given key. 87 | */ 88 | - (__nullable id)parameterValueForKey:(NSString *__nonnull)key; 89 | 90 | /** 91 | Set the attached CIFilter parameter value for the given key. 92 | */ 93 | - (void)setParameterValue:(__nullable id)value forKey:(NSString *__nonnull)key; 94 | 95 | /** 96 | Add a SCFilterAnimation that can animate parameter values. 97 | */ 98 | - (void)addAnimation:(SCFilterAnimation *__nonnull)animation; 99 | 100 | /** 101 | Convenience method to create and add an SCFilterAnimation that can animate parameter values. 102 | */ 103 | - (SCFilterAnimation *__nonnull)addAnimationForParameterKey:(NSString *__nonnull)key startValue:(__nullable id)startValue endValue:(__nullable id)endValue startTime:(CFTimeInterval)startTime duration:(CFTimeInterval)duration; 104 | 105 | /** 106 | Remove an already added SCFilterAnimation. 107 | */ 108 | - (void)removeAnimation:(SCFilterAnimation *__nonnull)animation; 109 | 110 | /** 111 | Remove all added SCFilterAnimation animations 112 | */ 113 | - (void)removeAllAnimations; 114 | 115 | /** 116 | Reset the attached CIFilter parameter values to default for this instance 117 | and all the sub filters. 118 | */ 119 | - (void)resetToDefaults; 120 | 121 | /** 122 | Add a sub filter. When processing an image, this SCFilter instance will first process the 123 | image using its attached CIFilter, then it will ask every sub filters added to process the 124 | given image. 125 | */ 126 | - (void)addSubFilter:(SCFilter *__nonnull)subFilter; 127 | 128 | /** 129 | Remove a sub filter. 130 | */ 131 | - (void)removeSubFilter:(SCFilter *__nonnull)subFilter; 132 | 133 | /** 134 | Remove a sub filter at a given index. 135 | */ 136 | - (void)removeSubFilterAtIndex:(NSInteger)index; 137 | 138 | /** 139 | Insert a sub filter at a given index. 140 | */ 141 | - (void)insertSubFilter:(SCFilter *__nonnull)subFilter atIndex:(NSInteger)index; 142 | 143 | /** 144 | Write this filter to a specific file. 145 | This filter can then be restored from this file using [SCFilter filterWithContentsOfUrl:]. 146 | */ 147 | - (void)writeToFile:(NSURL *__nonnull)fileUrl error:(NSError *__nullable*__nullable)error; 148 | 149 | /** 150 | Returns the CIImage by processing the given CIImage. 151 | */ 152 | - (CIImage *__nullable)imageByProcessingImage:(CIImage *__nullable)image; 153 | 154 | /** 155 | Returns the CIImage by processing the given CIImage with the given time. 156 | */ 157 | - (CIImage *__nullable)imageByProcessingImage:(CIImage *__nullable)image atTime:(CFTimeInterval)time; 158 | 159 | /** 160 | Creates and returns an empty SCFilter that has no CIFilter attached to it. 161 | It won't do anything when processing an image unless you add a non empty sub filter to it. 162 | */ 163 | + (SCFilter *__nonnull)emptyFilter; 164 | 165 | /** 166 | Creates and returns an SCFilter that will have the given CIFilter attached. 167 | */ 168 | + (SCFilter *__nonnull)filterWithCIFilter:(CIFilter *__nullable)CIFilter; 169 | 170 | /** 171 | Creates and returns an SCFilter attached to a newly created CIFilter from the given CIFilter name. 172 | */ 173 | + (SCFilter *__nonnull)filterWithCIFilterName:(NSString *__nonnull)name; 174 | 175 | /** 176 | Creates and returns an SCFilter that will process the images using the given affine transform. 177 | */ 178 | + (SCFilter *__nonnull)filterWithAffineTransform:(CGAffineTransform)affineTransform; 179 | 180 | /** 181 | Creates and returns a filter with a serialized filter data. 182 | */ 183 | + (SCFilter *__nonnull)filterWithData:(NSData *__nonnull)data; 184 | 185 | /** 186 | Creates and returns a filter with a serialized filter data. 187 | */ 188 | + (SCFilter *__nonnull)filterWithData:(NSData *__nonnull)data error:(NSError *__nullable*__nullable)error; 189 | 190 | /** 191 | Creates and returns a filter with an URL containing a serialized filter data. 192 | */ 193 | + (SCFilter *__nonnull)filterWithContentsOfURL:(NSURL *__nullable)url; 194 | 195 | /** 196 | Creates and returns a filter containg the given sub SCFilters. 197 | */ 198 | + (SCFilter *__nonnull)filterWithFilters:(NSArray *__nonnull)filters; 199 | 200 | /** 201 | Creates and returns a filter that will apply a CIImage on top 202 | */ 203 | + (SCFilter *__nonnull)filterWithCIImage:(CIImage *__nonnull)image; 204 | 205 | @end 206 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderTools.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderTools.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 24/12/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SCRecorderTools.h" 11 | #define kFULL_HD (1920 x 1080) 12 | #define kHD_READY (1280 x 720) 13 | 14 | @implementation SCRecorderTools 15 | 16 | + (BOOL)formatInRange:(AVCaptureDeviceFormat*)format frameRate:(CMTimeScale)frameRate { 17 | CMVideoDimensions dimensions; 18 | dimensions.width = 0; 19 | dimensions.height = 0; 20 | 21 | return [SCRecorderTools formatInRange:format frameRate:frameRate dimensions:dimensions]; 22 | } 23 | 24 | + (BOOL)formatInRange:(AVCaptureDeviceFormat*)format frameRate:(CMTimeScale)frameRate dimensions:(CMVideoDimensions)dimensions { 25 | CMVideoDimensions size = CMVideoFormatDescriptionGetDimensions(format.formatDescription); 26 | 27 | if (size.width >= dimensions.width && size.height >= dimensions.height) { 28 | for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { 29 | if (range.minFrameDuration.timescale >= frameRate && range.maxFrameDuration.timescale <= frameRate) { 30 | return YES; 31 | } 32 | } 33 | } 34 | 35 | return NO; 36 | } 37 | 38 | + (CMTimeScale)maxFrameRateForFormat:(AVCaptureDeviceFormat *)format minFrameRate:(CMTimeScale)minFrameRate { 39 | CMTimeScale lowerTimeScale = 0; 40 | for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { 41 | if (range.minFrameDuration.timescale >= minFrameRate && (lowerTimeScale == 0 || range.minFrameDuration.timescale < lowerTimeScale)) { 42 | lowerTimeScale = range.minFrameDuration.timescale; 43 | } 44 | } 45 | 46 | return lowerTimeScale; 47 | } 48 | 49 | + (AVCaptureDevice *)videoDeviceForPosition:(AVCaptureDevicePosition)position { 50 | NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; 51 | 52 | for (AVCaptureDevice *device in videoDevices) { 53 | if (device.position == (AVCaptureDevicePosition)position) { 54 | return device; 55 | } 56 | } 57 | 58 | return nil; 59 | } 60 | 61 | + (NSString *)captureSessionPresetForDimension:(CMVideoDimensions)videoDimension { 62 | if (videoDimension.width >= 1920 && videoDimension.height >= 1080) { 63 | return AVCaptureSessionPreset1920x1080; 64 | } 65 | if (videoDimension.width >= 1280 && videoDimension.height >= 720) { 66 | return AVCaptureSessionPreset1280x720; 67 | } 68 | if (videoDimension.width >= 960 && videoDimension.height >= 540) { 69 | return AVCaptureSessionPresetiFrame960x540; 70 | } 71 | if (videoDimension.width >= 640 && videoDimension.height >= 480) { 72 | return AVCaptureSessionPreset640x480; 73 | } 74 | if (videoDimension.width >= 352 && videoDimension.height >= 288) { 75 | return AVCaptureSessionPreset352x288; 76 | } 77 | 78 | return AVCaptureSessionPresetLow; 79 | } 80 | 81 | + (NSString *)bestCaptureSessionPresetForDevicePosition:(AVCaptureDevicePosition)devicePosition withMaxSize:(CGSize)maxSize { 82 | return [SCRecorderTools bestCaptureSessionPresetForDevice:[SCRecorderTools videoDeviceForPosition:devicePosition] withMaxSize:maxSize]; 83 | } 84 | 85 | + (NSString *)bestCaptureSessionPresetForDevice:(AVCaptureDevice *)device withMaxSize:(CGSize)maxSize { 86 | CMVideoDimensions highestDeviceDimension; 87 | highestDeviceDimension.width = 0; 88 | highestDeviceDimension.height = 0; 89 | 90 | for (AVCaptureDeviceFormat *format in device.formats) { 91 | CMVideoDimensions dimension = CMVideoFormatDescriptionGetDimensions(format.formatDescription); 92 | 93 | if (dimension.width <= (int)maxSize.width && dimension.height <= (int)maxSize.height && dimension.width * dimension.height > highestDeviceDimension.width * highestDeviceDimension.height) { 94 | highestDeviceDimension = dimension; 95 | } 96 | } 97 | 98 | return [SCRecorderTools captureSessionPresetForDimension:highestDeviceDimension]; 99 | } 100 | 101 | + (NSString *)bestCaptureSessionPresetCompatibleWithAllDevices { 102 | NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; 103 | 104 | CMVideoDimensions highestCompatibleDimension; 105 | BOOL lowestSet = NO; 106 | 107 | for (AVCaptureDevice *device in videoDevices) { 108 | CMVideoDimensions highestDeviceDimension; 109 | highestDeviceDimension.width = 0; 110 | highestDeviceDimension.height = 0; 111 | 112 | for (AVCaptureDeviceFormat *format in device.formats) { 113 | CMVideoDimensions dimension = CMVideoFormatDescriptionGetDimensions(format.formatDescription); 114 | 115 | if (dimension.width * dimension.height > highestDeviceDimension.width * highestDeviceDimension.height) { 116 | highestDeviceDimension = dimension; 117 | } 118 | } 119 | 120 | if (!lowestSet || (highestCompatibleDimension.width * highestCompatibleDimension.height > highestDeviceDimension.width * highestDeviceDimension.height)) { 121 | lowestSet = YES; 122 | highestCompatibleDimension = highestDeviceDimension; 123 | } 124 | 125 | } 126 | 127 | return [SCRecorderTools captureSessionPresetForDimension:highestCompatibleDimension]; 128 | } 129 | 130 | + (NSArray *)assetWriterMetadata { 131 | AVMutableMetadataItem *creationDate = [AVMutableMetadataItem new]; 132 | creationDate.keySpace = AVMetadataKeySpaceCommon; 133 | creationDate.key = AVMetadataCommonKeyCreationDate; 134 | creationDate.value = [[NSDate date] toISO8601]; 135 | 136 | AVMutableMetadataItem *software = [AVMutableMetadataItem new]; 137 | software.keySpace = AVMetadataKeySpaceCommon; 138 | software.key = AVMetadataCommonKeySoftware; 139 | software.value = @"SCRecorder"; 140 | 141 | return @[software, creationDate]; 142 | } 143 | 144 | @end 145 | 146 | @implementation NSDate (SCRecorderTools) 147 | 148 | + (NSDateFormatter *)_getFormatter { 149 | static NSDateFormatter *dateFormatter = nil; 150 | static dispatch_once_t onceToken; 151 | dispatch_once(&onceToken, ^{ 152 | dateFormatter = [[NSDateFormatter alloc] init]; 153 | NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 154 | [dateFormatter setLocale:enUSPOSIXLocale]; 155 | [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"]; 156 | }); 157 | 158 | return dateFormatter; 159 | } 160 | 161 | - (NSString*)toISO8601 { 162 | return [[NSDate _getFormatter] stringFromDate:self]; 163 | } 164 | 165 | + (NSDate *)fromISO8601:(NSString *)iso8601 { 166 | return [[NSDate _getFormatter] dateFromString:iso8601]; 167 | } 168 | 169 | @end 170 | 171 | -------------------------------------------------------------------------------- /Library/Sources/SCSwipeableFilterView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCFilterSwitcherView.m 3 | // SCRecorderExamples 4 | // 5 | // Created by Simon CORSIN on 29/05/14. 6 | // 7 | // 8 | 9 | #import "SCSwipeableFilterView.h" 10 | #import "SCSampleBufferHolder.h" 11 | 12 | @interface SCSwipeableFilterView() { 13 | } 14 | 15 | @end 16 | 17 | @implementation SCSwipeableFilterView 18 | 19 | - (id)initWithFrame:(CGRect)frame { 20 | self = [super initWithFrame:frame]; 21 | 22 | if (self) { 23 | [self _swipeableCommonInit]; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | - (id)initWithCoder:(NSCoder *)aDecoder { 30 | self = [super initWithCoder:aDecoder]; 31 | 32 | if (self) { 33 | [self _swipeableCommonInit]; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | - (void)dealloc { 40 | 41 | } 42 | 43 | - (void)_swipeableCommonInit { 44 | _refreshAutomaticallyWhenScrolling = YES; 45 | _selectFilterScrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; 46 | _selectFilterScrollView.delegate = self; 47 | _selectFilterScrollView.pagingEnabled = YES; 48 | _selectFilterScrollView.showsHorizontalScrollIndicator = NO; 49 | _selectFilterScrollView.showsVerticalScrollIndicator = NO; 50 | _selectFilterScrollView.bounces = YES; 51 | _selectFilterScrollView.alwaysBounceHorizontal = YES; 52 | _selectFilterScrollView.alwaysBounceVertical = YES; 53 | _selectFilterScrollView.backgroundColor = [UIColor clearColor]; 54 | 55 | [self addSubview:_selectFilterScrollView]; 56 | } 57 | 58 | - (void)layoutSubviews { 59 | [super layoutSubviews]; 60 | 61 | _selectFilterScrollView.frame = self.bounds; 62 | 63 | [self updateScrollViewContentSize]; 64 | } 65 | 66 | - (void)updateScrollViewContentSize { 67 | _selectFilterScrollView.contentSize = CGSizeMake(self.filters.count * self.frame.size.width * 3, self.frame.size.height); 68 | 69 | if (self.selectedFilter != nil) { 70 | [self scrollToFilter:self.selectedFilter animated:NO]; 71 | } 72 | } 73 | 74 | - (void)scrollToFilter:(SCFilter *)filter animated:(BOOL)animated { 75 | NSInteger index = [self.filters indexOfObject:filter]; 76 | if (index >= 0) { 77 | CGPoint contentOffset = CGPointMake(_selectFilterScrollView.contentSize.width / 3 + _selectFilterScrollView.frame.size.width * index, 0); 78 | [_selectFilterScrollView setContentOffset:contentOffset animated:animated]; 79 | [self updateCurrentSelected:NO]; 80 | } else { 81 | [NSException raise:@"InvalidFilterException" format:@"This filter is not present in the filters array"]; 82 | } 83 | } 84 | 85 | - (void)updateCurrentSelected:(BOOL)shouldNotify { 86 | NSUInteger filterGroupsCount = self.filters.count; 87 | NSInteger selectedIndex = (NSInteger)((_selectFilterScrollView.contentOffset.x + _selectFilterScrollView.frame.size.width / 2) / _selectFilterScrollView.frame.size.width) % filterGroupsCount; 88 | SCFilter *newFilterGroup = nil; 89 | 90 | if (selectedIndex >= 0 && selectedIndex < filterGroupsCount) { 91 | newFilterGroup = [self.filters objectAtIndex:selectedIndex]; 92 | } else { 93 | NSLog(@"Invalid contentOffset of scrollView in SCFilterSwitcherView (%f/%f with %d)", _selectFilterScrollView.contentOffset.x, _selectFilterScrollView.contentOffset.y, (int)self.filters.count); 94 | } 95 | 96 | if (self.selectedFilter != newFilterGroup) { 97 | [self setSelectedFilter:newFilterGroup]; 98 | 99 | if (shouldNotify) { 100 | id del = self.delegate; 101 | 102 | if ([del respondsToSelector:@selector(swipeableFilterView:didScrollToFilter:)]) { 103 | [del swipeableFilterView:self didScrollToFilter:newFilterGroup]; 104 | } 105 | } 106 | } 107 | } 108 | 109 | - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { 110 | [self updateCurrentSelected:YES]; 111 | } 112 | 113 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { 114 | [self updateCurrentSelected:YES]; 115 | } 116 | 117 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 118 | [self updateCurrentSelected:YES]; 119 | } 120 | 121 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 122 | if (!decelerate) { 123 | [self updateCurrentSelected:YES]; 124 | } 125 | } 126 | 127 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 128 | CGFloat width = scrollView.frame.size.width; 129 | CGFloat contentOffsetX = scrollView.contentOffset.x; 130 | CGFloat contentSizeWidth = scrollView.contentSize.width; 131 | CGFloat normalWidth = self.filters.count * width; 132 | 133 | if (width > 0 && contentSizeWidth > 0) { 134 | if (contentOffsetX <= 0) { 135 | scrollView.contentOffset = CGPointMake(contentOffsetX + normalWidth, scrollView.contentOffset.y); 136 | } else if (contentOffsetX + width >= contentSizeWidth) { 137 | scrollView.contentOffset = CGPointMake(contentOffsetX - normalWidth, scrollView.contentOffset.y); 138 | } 139 | } 140 | 141 | if (_refreshAutomaticallyWhenScrolling) { 142 | [self setNeedsDisplay]; 143 | } 144 | } 145 | 146 | - (CIImage *)renderedCIImageInRect:(CGRect)rect { 147 | CIImage *image = [super renderedCIImageInRect:rect]; 148 | 149 | CFTimeInterval imageTime = self.CIImageTime; 150 | if (self.preprocessingFilter != nil) { 151 | image = [self.preprocessingFilter imageByProcessingImage:image atTime:imageTime]; 152 | } 153 | 154 | CGRect extent = [image extent]; 155 | 156 | 157 | CGSize contentSize = _selectFilterScrollView.frame.size; 158 | 159 | if (contentSize.width == 0) { 160 | return image; 161 | } 162 | 163 | CGFloat ratio = _selectFilterScrollView.contentOffset.x / contentSize.width; 164 | 165 | NSInteger index = (NSInteger)ratio; 166 | NSInteger upIndex = (NSInteger)ceilf(ratio); 167 | CGFloat remainingRatio = ratio - ((CGFloat)index); 168 | 169 | NSArray *filters = self.filters; 170 | 171 | CGFloat xImage = extent.size.width * -remainingRatio; 172 | CIImage *outputImage = [CIImage imageWithColor:[CIColor colorWithRed:0 green:0 blue:0]]; 173 | 174 | while (index <= upIndex) { 175 | NSInteger currentIndex = index % filters.count; 176 | SCFilter *filter = [filters objectAtIndex:currentIndex]; 177 | CIImage *filteredImage = [filter imageByProcessingImage:image atTime:imageTime]; 178 | filteredImage = [filteredImage imageByCroppingToRect:CGRectMake(extent.origin.x + xImage, extent.origin.y, extent.size.width, extent.size.height)]; 179 | outputImage = [filteredImage imageByCompositingOverImage:outputImage]; 180 | xImage += extent.size.width; 181 | index++; 182 | } 183 | outputImage = [outputImage imageByCroppingToRect:extent]; 184 | 185 | return outputImage; 186 | } 187 | 188 | - (void)setFilters:(NSArray *)filters { 189 | _filters = filters; 190 | [self updateScrollViewContentSize]; 191 | [self updateCurrentSelected:YES]; 192 | } 193 | 194 | - (void)setSelectedFilter:(SCFilter *)selectedFilter { 195 | if (_selectedFilter != selectedFilter) { 196 | [self willChangeValueForKey:@"selectedFilter"]; 197 | _selectedFilter = selectedFilter; 198 | 199 | [self didChangeValueForKey:@"selectedFilter"]; 200 | 201 | [self setNeedsLayout]; 202 | } 203 | } 204 | 205 | @end 206 | -------------------------------------------------------------------------------- /Library/Sources/SCRecordSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCSession.h 3 | // SCAudioVideoRecorder 4 | // 5 | // Created by Simon CORSIN on 27/03/14. 6 | // Copyright (c) 2014 rFlex. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SCRecordSessionSegment.h" 12 | 13 | #define kRecordSessionDefaultVideoCodec AVVideoCodecH264 14 | #define kRecordSessionDefaultVideoScalingMode AVVideoScalingModeResizeAspectFill 15 | #define kRecordSessionDefaultOutputBitPerPixel 12 16 | #define kRecordSessionDefaultAudioBitrate 128000 17 | #define kRecordSessionDefaultAudioFormat kAudioFormatMPEG4AAC 18 | 19 | extern NSString *__nonnull const SCRecordSessionSegmentFilenameKey; 20 | extern NSString *__nonnull const SCRecordSessionSegmentFilenamesKey; 21 | extern NSString *__nonnull const SCRecordSessionSegmentsKey; 22 | extern NSString *__nonnull const SCRecordSessionDurationKey; 23 | extern NSString *__nonnull const SCRecordSessionIdentifierKey; 24 | extern NSString *__nonnull const SCRecordSessionSegmentInfoKey; 25 | extern NSString *__nonnull const SCRecordSessionDateKey; 26 | extern NSString *__nonnull const SCRecordSessionDirectoryKey; 27 | 28 | extern NSString *__nonnull const SCRecordSessionTemporaryDirectory; 29 | extern NSString *__nonnull const SCRecordSessionCacheDirectory; 30 | extern NSString *__nonnull const SCRecordSessionDocumentDirectory; 31 | 32 | @class SCRecordSession; 33 | @class SCRecorder; 34 | 35 | @interface SCRecordSession : NSObject 36 | 37 | ////////////////// 38 | // GENERAL SETTINGS 39 | //// 40 | 41 | /** 42 | An unique identifier generated when creating this record session. 43 | */ 44 | @property (readonly, nonatomic) NSString *__nonnull identifier; 45 | 46 | /** 47 | The date when this record session was created. 48 | */ 49 | @property (readonly, nonatomic) NSDate *__nonnull date; 50 | 51 | /** 52 | The directory to which the record segments will be saved. 53 | Can be either SCRecordSessionTemporaryDirectory or an arbritary directory. 54 | Default is SCRecordSessionTemporaryDirectory. 55 | */ 56 | @property (copy, nonatomic) NSString *__nonnull segmentsDirectory; 57 | 58 | /** 59 | The output file type used for the AVAssetWriter. 60 | If null, AVFileTypeMPEG4 will be used for a video file, AVFileTypeAppleM4A for an audio file 61 | */ 62 | @property (copy, nonatomic) NSString *__nullable fileType; 63 | 64 | /** 65 | The extension of every record segments. 66 | If null, the SCRecordSession will figure out one depending on the fileType. 67 | */ 68 | @property (copy, nonatomic) NSString *__nullable fileExtension; 69 | 70 | /** 71 | The output url based on the identifier, the recordSegmentsDirectory and the fileExtension 72 | */ 73 | @property (readonly, nonatomic) NSURL *__nonnull outputUrl; 74 | 75 | /** 76 | Contains every record segment as SCRecordSessionSegment. 77 | */ 78 | @property (readonly, nonatomic) NSArray *__nonnull segments; 79 | 80 | /** 81 | The duration of the whole recordSession including the current recording segment 82 | and the previously added record segments. 83 | */ 84 | @property (readonly, nonatomic) CMTime duration; 85 | 86 | /** 87 | The duration of the recorded record segments. 88 | */ 89 | @property (readonly, atomic) CMTime segmentsDuration; 90 | 91 | /** 92 | The duration of the current recording segment. 93 | */ 94 | @property (readonly, atomic) CMTime currentSegmentDuration; 95 | 96 | /** 97 | True if a recordSegment has began 98 | */ 99 | @property (readonly, nonatomic) BOOL recordSegmentBegan; 100 | 101 | /** 102 | The recorder that is managing this SCRecordSession 103 | */ 104 | @property (readonly, nonatomic, weak) SCRecorder *__nullable recorder; 105 | 106 | ////////////////// 107 | // PUBLIC METHODS 108 | //// 109 | 110 | - (nonnull instancetype)init; 111 | 112 | - (nonnull instancetype)initWithDictionaryRepresentation:(nonnull NSDictionary *)dictionaryRepresentation; 113 | 114 | /** 115 | Create a SCRecordSession 116 | */ 117 | + (nonnull instancetype)recordSession; 118 | 119 | /** 120 | Create a SCRecordSession based on dictionary representation 121 | */ 122 | + (nonnull instancetype)recordSession:(nonnull NSDictionary *)dictionaryRepresentation; 123 | 124 | /** 125 | Calling any method of SCRecordSession is thread safe. However, 126 | if the record session is inside an SCRecorder instance, its state 127 | might change between 2 calls you are making. Making any modification 128 | within this block will ensure that you are the only one who has 129 | access to any modification on this SCRecordSession. 130 | */ 131 | - (void)dispatchSyncOnSessionQueue:(void(^__nonnull)())block; 132 | 133 | ////////////////////// 134 | /////// SEGMENTS 135 | //// 136 | 137 | /** 138 | Remove the record segment. Does not delete the associated file. 139 | */ 140 | - (void)removeSegment:(SCRecordSessionSegment *__nonnull)segment; 141 | 142 | /** 143 | Remove the record segment at the given index. 144 | */ 145 | - (void)removeSegmentAtIndex:(NSInteger)segmentIndex deleteFile:(BOOL)deleteFile; 146 | 147 | /** 148 | Add a recorded segment. 149 | */ 150 | - (void)addSegment:(SCRecordSessionSegment *__nonnull)segment; 151 | 152 | /** 153 | Insert a record segment. 154 | */ 155 | - (void)insertSegment:(SCRecordSessionSegment *__nonnull)segment atIndex:(NSInteger)segmentIndex; 156 | 157 | /** 158 | Remove all the record segments and their associated files. 159 | */ 160 | - (void)removeAllSegments; 161 | 162 | /** 163 | Remove all the record segments and their associated files if deleteFiles is true. 164 | */ 165 | - (void)removeAllSegments:(BOOL)deleteFiles; 166 | 167 | /** 168 | Remove the last segment safely. Does nothing if no segment were recorded. 169 | */ 170 | - (void)removeLastSegment; 171 | 172 | /** 173 | Cancel the session. 174 | End the current recordSegment (if any) and call removeAllSegments 175 | If you don't want a segment to be automatically added when calling this method, 176 | you should remove the SCRecordSession from the SCRecorder 177 | */ 178 | - (void)cancelSession:(void(^ __nullable)())completionHandler; 179 | 180 | /** 181 | Merge the recorded record segments using the given AVAssetExportSessionPreset. 182 | Returns the AVAssetExportSession used for exporting. 183 | Returns nil and call the completion handler block synchronously if an error happened while preparing the export session. 184 | */ 185 | - (AVAssetExportSession *__nullable)mergeSegmentsUsingPreset:(NSString *__nonnull)exportSessionPreset completionHandler:(void(^__nonnull)(NSURL *__nullable outputUrl, NSError *__nullable error))completionHandler; 186 | 187 | /** 188 | Returns an asset representing all the record segments 189 | from this record session. This can be called anytime. 190 | */ 191 | - (AVAsset *__nonnull)assetRepresentingSegments; 192 | 193 | /** 194 | Returns a player item representing all the record segments 195 | from this record session and containing an audio mix that smooth 196 | the transition between the segments. 197 | */ 198 | - (AVPlayerItem *__nonnull)playerItemRepresentingSegments; 199 | 200 | /** 201 | Append all the record segments to a given AVMutableComposition. 202 | */ 203 | - (void)appendSegmentsToComposition:(AVMutableComposition *__nonnull)composition; 204 | 205 | /** 206 | Append all the record segments to a given AVMutableComposition and adds the audio mix instruction 207 | if audioMix is provided 208 | */ 209 | - (void)appendSegmentsToComposition:(AVMutableComposition *__nonnull)composition audioMix:(AVMutableAudioMix *__nullable)audioMix; 210 | 211 | /** 212 | Returns a dictionary that represents this SCRecordSession 213 | This will only contains strings and can be therefore safely serialized 214 | in any text format 215 | */ 216 | - (NSDictionary *__nonnull)dictionaryRepresentation; 217 | 218 | /** 219 | Stop the current segment and deinitialize the video and the audio. 220 | This can be useful if the input video or audio profile changed. 221 | */ 222 | - (void)deinitialize; 223 | 224 | /** 225 | Start a new record segment. 226 | This method is automatically called by the SCRecorder. 227 | */ 228 | - (void)beginSegment:(NSError*__nullable*__nullable)error; 229 | 230 | /** 231 | End the current record segment. 232 | This method is automatically called by the SCRecorder 233 | when calling [SCRecorder pause] if necessary. 234 | segmentIndex contains the index of the segment recorded accessible 235 | in the recordSegments array. If error is not null, if will be -1 236 | If you don't remove the SCRecordSession from the SCRecorder while calling this method, 237 | The SCRecorder might create a new recordSegment right after automatically if it is not paused. 238 | */ 239 | - (BOOL)endSegmentWithInfo:(NSDictionary *__nullable)info completionHandler:(void(^__nullable)(SCRecordSessionSegment *__nullable segment, NSError *__nullable error))completionHandler; 240 | 241 | @end 242 | -------------------------------------------------------------------------------- /Library/Sources/SCRecorderToolsView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCRecorderToolsView.m 3 | // SCRecorder 4 | // 5 | // Created by Simon CORSIN on 16/02/15. 6 | // Copyright (c) 2015 rFlex. All rights reserved. 7 | // 8 | 9 | #import "SCRecorderToolsView.h" 10 | #import "SCRecorderFocusTargetView.h" 11 | 12 | #define BASE_FOCUS_TARGET_WIDTH 60 13 | #define BASE_FOCUS_TARGET_HEIGHT 60 14 | #define kDefaultMinZoomFactor 1 15 | #define kDefaultMaxZoomFactor 4 16 | 17 | @interface SCRecorderToolsView() 18 | { 19 | UITapGestureRecognizer *_tapToFocusGesture; 20 | UITapGestureRecognizer *_doubleTapToResetFocusGesture; 21 | UIPinchGestureRecognizer *_pinchZoomGesture; 22 | CGFloat _zoomAtStart; 23 | } 24 | 25 | @property (strong, nonatomic) SCRecorderFocusTargetView *cameraFocusTargetView; 26 | 27 | @end 28 | 29 | @implementation SCRecorderToolsView 30 | 31 | static char *ContextAdjustingFocus = "AdjustingFocus"; 32 | static char *ContextAdjustingExposure = "AdjustingExposure"; 33 | static char *ContextDidChangeDevice = "DidChangeDevice"; 34 | 35 | - (id)initWithFrame:(CGRect)frame { 36 | self = [super initWithFrame:frame]; 37 | 38 | if (self) { 39 | [self commonInit]; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | - (id)initWithCoder:(NSCoder *)aDecoder { 46 | self = [super initWithCoder:aDecoder]; 47 | 48 | if (self) { 49 | [self commonInit]; 50 | } 51 | 52 | return self; 53 | } 54 | 55 | - (void)dealloc { 56 | self.recorder = nil; 57 | } 58 | 59 | - (void)commonInit { 60 | _minZoomFactor = kDefaultMinZoomFactor; 61 | _maxZoomFactor = kDefaultMaxZoomFactor; 62 | self.showsFocusAnimationAutomatically = YES; 63 | self.cameraFocusTargetView = [[SCRecorderFocusTargetView alloc] init]; 64 | self.cameraFocusTargetView.hidden = YES; 65 | [self addSubview:self.cameraFocusTargetView]; 66 | 67 | self.focusTargetSize = CGSizeMake(BASE_FOCUS_TARGET_WIDTH, BASE_FOCUS_TARGET_HEIGHT); 68 | 69 | _tapToFocusGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToAutoFocus:)]; 70 | [self addGestureRecognizer:_tapToFocusGesture]; 71 | 72 | _doubleTapToResetFocusGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToContinouslyAutoFocus:)]; 73 | _doubleTapToResetFocusGesture.numberOfTapsRequired = 2; 74 | [_tapToFocusGesture requireGestureRecognizerToFail:_doubleTapToResetFocusGesture]; 75 | 76 | [self addGestureRecognizer:_doubleTapToResetFocusGesture]; 77 | 78 | _pinchZoomGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchToZoom:)]; 79 | 80 | [self addGestureRecognizer:_pinchZoomGesture]; 81 | } 82 | 83 | - (void)showFocusAnimation { 84 | [self adjustFocusView]; 85 | self.cameraFocusTargetView.hidden = NO; 86 | [self.cameraFocusTargetView startTargeting]; 87 | } 88 | 89 | - (void)hideFocusAnimation { 90 | [self.cameraFocusTargetView stopTargeting]; 91 | } 92 | 93 | - (void)layoutSubviews { 94 | [super layoutSubviews]; 95 | 96 | [self adjustFocusView]; 97 | } 98 | 99 | - (void)adjustFocusView { 100 | CGPoint currentFocusPoint = CGPointMake(0.5, 0.5); 101 | 102 | if (self.recorder.focusSupported) { 103 | currentFocusPoint = self.recorder.focusPointOfInterest; 104 | } else if (self.recorder.exposureSupported) { 105 | currentFocusPoint = self.recorder.exposurePointOfInterest; 106 | } 107 | 108 | CGPoint viewPoint = [self.recorder convertPointOfInterestToViewCoordinates:currentFocusPoint]; 109 | viewPoint = [self convertPoint:viewPoint fromView:self.recorder.previewView]; 110 | self.cameraFocusTargetView.center = viewPoint; 111 | } 112 | 113 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 114 | if (context == ContextAdjustingFocus) { 115 | if (self.showsFocusAnimationAutomatically) { 116 | if (self.recorder.isAdjustingFocus) { 117 | [self showFocusAnimation]; 118 | } else { 119 | [self hideFocusAnimation]; 120 | } 121 | } 122 | } else if (context == ContextAdjustingExposure) { 123 | if (self.showsFocusAnimationAutomatically && !self.recorder.focusSupported) { 124 | if (self.recorder.isAdjustingExposure) { 125 | [self showFocusAnimation]; 126 | } else { 127 | [self hideFocusAnimation]; 128 | } 129 | } 130 | } else if (context == ContextDidChangeDevice) { 131 | [self hideFocusAnimation]; 132 | } 133 | } 134 | 135 | // Auto focus at a particular point. The focus mode will change to locked once the auto focus happens. 136 | - (void)tapToAutoFocus:(UIGestureRecognizer *)gestureRecognizer { 137 | SCRecorder *recorder = self.recorder; 138 | 139 | CGPoint tapPoint = [gestureRecognizer locationInView:recorder.previewView]; 140 | CGPoint convertedFocusPoint = [recorder convertToPointOfInterestFromViewCoordinates:tapPoint]; 141 | [recorder autoFocusAtPoint:convertedFocusPoint]; 142 | 143 | id delegate = self.delegate; 144 | if ([delegate respondsToSelector:@selector(recorderToolsView:didTapToFocusWithGestureRecognizer:)]) { 145 | [delegate recorderToolsView:self didTapToFocusWithGestureRecognizer:gestureRecognizer]; 146 | } 147 | } 148 | 149 | // Change to continuous auto focus. The camera will constantly focus at the point choosen. 150 | - (void)tapToContinouslyAutoFocus:(UIGestureRecognizer *)gestureRecognizer { 151 | SCRecorder *recorder = self.recorder; 152 | if (recorder.focusSupported) { 153 | self.cameraFocusTargetView.center = self.center; 154 | [recorder continuousFocusAtPoint:CGPointMake(.5f, .5f)]; 155 | } 156 | } 157 | 158 | - (void)pinchToZoom:(UIPinchGestureRecognizer *)gestureRecognizer { 159 | SCRecorder *strongRecorder = self.recorder; 160 | 161 | if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { 162 | _zoomAtStart = strongRecorder.videoZoomFactor; 163 | } 164 | 165 | CGFloat newZoom = gestureRecognizer.scale * _zoomAtStart; 166 | 167 | if (newZoom > _maxZoomFactor) { 168 | newZoom = _maxZoomFactor; 169 | } else if (newZoom < _minZoomFactor) { 170 | newZoom = _minZoomFactor; 171 | } 172 | 173 | strongRecorder.videoZoomFactor = newZoom; 174 | } 175 | 176 | - (void)setFocusTargetSize:(CGSize)focusTargetSize { 177 | CGRect rect = self.cameraFocusTargetView.frame; 178 | rect.size = focusTargetSize; 179 | self.cameraFocusTargetView.frame = rect; 180 | [self adjustFocusView]; 181 | } 182 | 183 | - (CGSize)focusTargetSize { 184 | return self.cameraFocusTargetView.frame.size; 185 | } 186 | 187 | - (UIImage*)outsideFocusTargetImage { 188 | return self.cameraFocusTargetView.outsideFocusTargetImage; 189 | } 190 | 191 | - (void)setOutsideFocusTargetImage:(UIImage *)outsideFocusTargetImage { 192 | self.cameraFocusTargetView.outsideFocusTargetImage = outsideFocusTargetImage; 193 | } 194 | 195 | - (UIImage*)insideFocusTargetImage { 196 | return self.cameraFocusTargetView.insideFocusTargetImage; 197 | } 198 | 199 | - (void)setInsideFocusTargetImage:(UIImage *)insideFocusTargetImage { 200 | self.cameraFocusTargetView.insideFocusTargetImage = insideFocusTargetImage; 201 | } 202 | 203 | - (BOOL)tapToFocusEnabled { 204 | return _tapToFocusGesture.enabled; 205 | } 206 | 207 | - (void)setTapToFocusEnabled:(BOOL)tapToFocusEnabled { 208 | _tapToFocusGesture.enabled = tapToFocusEnabled; 209 | } 210 | 211 | - (BOOL)doubleTapToResetFocusEnabled { 212 | return _doubleTapToResetFocusGesture.enabled; 213 | } 214 | 215 | - (void)setDoubleTapToResetFocusEnabled:(BOOL)doubleTapToResetFocusEnabled { 216 | _doubleTapToResetFocusGesture.enabled = doubleTapToResetFocusEnabled; 217 | } 218 | 219 | - (BOOL)pinchToZoomEnabled { 220 | return _pinchZoomGesture.enabled; 221 | } 222 | 223 | - (void)setPinchToZoomEnabled:(BOOL)pinchToZoomEnabled { 224 | _pinchZoomGesture.enabled = pinchToZoomEnabled; 225 | } 226 | 227 | - (void)setRecorder:(SCRecorder *)recorder { 228 | SCRecorder *oldRecorder = _recorder; 229 | 230 | if (oldRecorder != nil) { 231 | [oldRecorder removeObserver:self forKeyPath:@"isAdjustingFocus"]; 232 | [oldRecorder removeObserver:self forKeyPath:@"isAdjustingExposure"]; 233 | [oldRecorder removeObserver:self forKeyPath:@"device"]; 234 | } 235 | 236 | _recorder = recorder; 237 | 238 | if (recorder != nil) { 239 | [recorder addObserver:self forKeyPath:@"isAdjustingFocus" options:NSKeyValueObservingOptionNew context:ContextAdjustingFocus]; 240 | [recorder addObserver:self forKeyPath:@"isAdjustingExposure" options:NSKeyValueObservingOptionNew context:ContextAdjustingExposure]; 241 | [recorder addObserver:self forKeyPath:@"device" options:NSKeyValueObservingOptionNew context:ContextDidChangeDevice]; 242 | } 243 | } 244 | 245 | @end 246 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | --------------------------------------------------------------------------------