AVClub 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/AVClub.gemspec +26 -0
- data/README.md +56 -0
- data/Rakefile +23 -0
- data/app/app_delegate.rb +8 -0
- data/app/camera_controller.rb +193 -0
- data/lib/AVClub/AVClubController.rb +69 -0
- data/lib/AVClub/version.rb +3 -0
- data/lib/avclub.rb +35 -0
- data/spec/main_spec.rb +9 -0
- data/vendor/AVClub/AVClub.xcodeproj/project.pbxproj +290 -0
- data/vendor/AVClub/AVClub.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- data/vendor/AVClub/AVClub.xcodeproj/project.xcworkspace/xcuserdata/colinta.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/vendor/AVClub/AVClub.xcodeproj/project.xcworkspace/xcuserdata/colinta.xcuserdatad/WorkspaceSettings.xcsettings +10 -0
- data/vendor/AVClub/AVClub.xcodeproj/xcuserdata/colinta.xcuserdatad/xcschemes/AVClub.xcscheme +59 -0
- data/vendor/AVClub/AVClub.xcodeproj/xcuserdata/colinta.xcuserdatad/xcschemes/xcschememanagement.plist +22 -0
- data/vendor/AVClub/AVClub/AVCamRecorder.h +80 -0
- data/vendor/AVClub/AVClub/AVCamRecorder.m +155 -0
- data/vendor/AVClub/AVClub/AVCamUtilities.h +61 -0
- data/vendor/AVClub/AVClub/AVCamUtilities.m +65 -0
- data/vendor/AVClub/AVClub/AVClub-Prefix.pch +9 -0
- data/vendor/AVClub/AVClub/AVClub.h +72 -0
- data/vendor/AVClub/AVClub/AVClub.m +673 -0
- metadata +108 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
/*
|
2
|
+
File: AVCamRecorder.m
|
3
|
+
Abstract: An interface to manage the use of AVCaptureMovieFileOutput for recording videos. Its responsibilities include
|
4
|
+
configuring the AVCaptureMovieFileOutput, adding it to the desired capture session, and starting and stopping video recordings.
|
5
|
+
Version: 1.2
|
6
|
+
|
7
|
+
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
|
8
|
+
Inc. ("Apple") in consideration of your agreement to the following
|
9
|
+
terms, and your use, installation, modification or redistribution of
|
10
|
+
this Apple software constitutes acceptance of these terms. If you do
|
11
|
+
not agree with these terms, please do not use, install, modify or
|
12
|
+
redistribute this Apple software.
|
13
|
+
|
14
|
+
In consideration of your agreement to abide by the following terms, and
|
15
|
+
subject to these terms, Apple grants you a personal, non-exclusive
|
16
|
+
license, under Apple's copyrights in this original Apple software (the
|
17
|
+
"Apple Software"), to use, reproduce, modify and redistribute the Apple
|
18
|
+
Software, with or without modifications, in source and/or binary forms;
|
19
|
+
provided that if you redistribute the Apple Software in its entirety and
|
20
|
+
without modifications, you must retain this notice and the following
|
21
|
+
text and disclaimers in all such redistributions of the Apple Software.
|
22
|
+
Neither the name, trademarks, service marks or logos of Apple Inc. may
|
23
|
+
be used to endorse or promote products derived from the Apple Software
|
24
|
+
without specific prior written permission from Apple. Except as
|
25
|
+
expressly stated in this notice, no other rights or licenses, express or
|
26
|
+
implied, are granted by Apple herein, including but not limited to any
|
27
|
+
patent rights that may be infringed by your derivative works or by other
|
28
|
+
works in which the Apple Software may be incorporated.
|
29
|
+
|
30
|
+
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
|
31
|
+
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
|
32
|
+
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
|
33
|
+
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
|
34
|
+
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
|
35
|
+
|
36
|
+
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
|
37
|
+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
38
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
39
|
+
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
|
40
|
+
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
|
41
|
+
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
|
42
|
+
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
|
43
|
+
POSSIBILITY OF SUCH DAMAGE.
|
44
|
+
|
45
|
+
Copyright (C) 2011 Apple Inc. All Rights Reserved.
|
46
|
+
|
47
|
+
*/
|
48
|
+
|
49
|
+
#import "AVCamRecorder.h"
|
50
|
+
#import "AVCamUtilities.h"
|
51
|
+
|
52
|
+
@interface AVCamRecorder (FileOutputDelegate) <AVCaptureFileOutputRecordingDelegate>
|
53
|
+
@end
|
54
|
+
|
55
|
+
@implementation AVCamRecorder
|
56
|
+
|
57
|
+
@synthesize session;
|
58
|
+
@synthesize movieFileOutput;
|
59
|
+
@synthesize outputFileURL;
|
60
|
+
@synthesize delegate;
|
61
|
+
|
62
|
+
- (id) initWithSession:(AVCaptureSession *)aSession outputFileURL:(NSURL *)anOutputFileURL
|
63
|
+
{
|
64
|
+
self = [super init];
|
65
|
+
if (self != nil) {
|
66
|
+
AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
|
67
|
+
if ([aSession canAddOutput:aMovieFileOutput])
|
68
|
+
[aSession addOutput:aMovieFileOutput];
|
69
|
+
[self setMovieFileOutput:aMovieFileOutput];
|
70
|
+
|
71
|
+
[self setSession:aSession];
|
72
|
+
[self setOutputFileURL:anOutputFileURL];
|
73
|
+
}
|
74
|
+
|
75
|
+
return self;
|
76
|
+
}
|
77
|
+
|
78
|
+
- (void) dealloc
|
79
|
+
{
|
80
|
+
[[self session] removeOutput:[self movieFileOutput]];
|
81
|
+
}
|
82
|
+
|
83
|
+
-(BOOL)isOrientationSupported
|
84
|
+
{
|
85
|
+
AVCaptureConnection *videoConnection = [AVCamUtilities connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self movieFileOutput] connections]];
|
86
|
+
return [videoConnection isVideoOrientationSupported];
|
87
|
+
}
|
88
|
+
|
89
|
+
-(void)setOrientation:(AVCaptureVideoOrientation)orientation
|
90
|
+
{
|
91
|
+
AVCaptureConnection *videoConnection = [AVCamUtilities connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self movieFileOutput] connections]];
|
92
|
+
return [videoConnection setVideoOrientation:orientation];
|
93
|
+
}
|
94
|
+
|
95
|
+
-(BOOL)isMirrored
|
96
|
+
{
|
97
|
+
AVCaptureConnection *videoConnection = [AVCamUtilities connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self movieFileOutput] connections]];
|
98
|
+
return [videoConnection isVideoMirrored];
|
99
|
+
}
|
100
|
+
|
101
|
+
-(BOOL)recordsVideo
|
102
|
+
{
|
103
|
+
AVCaptureConnection *videoConnection = [AVCamUtilities connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self movieFileOutput] connections]];
|
104
|
+
return [videoConnection isActive];
|
105
|
+
}
|
106
|
+
|
107
|
+
-(BOOL)recordsAudio
|
108
|
+
{
|
109
|
+
AVCaptureConnection *audioConnection = [AVCamUtilities connectionWithMediaType:AVMediaTypeAudio fromConnections:[[self movieFileOutput] connections]];
|
110
|
+
return [audioConnection isActive];
|
111
|
+
}
|
112
|
+
|
113
|
+
-(BOOL)isRecording
|
114
|
+
{
|
115
|
+
return [[self movieFileOutput] isRecording];
|
116
|
+
}
|
117
|
+
|
118
|
+
-(void)startRecordingWithOrientation:(AVCaptureVideoOrientation)videoOrientation;
|
119
|
+
{
|
120
|
+
AVCaptureConnection *videoConnection = [AVCamUtilities connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self movieFileOutput] connections]];
|
121
|
+
if ([videoConnection isVideoOrientationSupported])
|
122
|
+
[videoConnection setVideoOrientation:videoOrientation];
|
123
|
+
|
124
|
+
[[self movieFileOutput] startRecordingToOutputFileURL:[self outputFileURL] recordingDelegate:self];
|
125
|
+
}
|
126
|
+
|
127
|
+
-(void)stopRecording
|
128
|
+
{
|
129
|
+
[[self movieFileOutput] stopRecording];
|
130
|
+
}
|
131
|
+
|
132
|
+
@end
|
133
|
+
|
134
|
+
@implementation AVCamRecorder (FileOutputDelegate)
|
135
|
+
|
136
|
+
- (void) captureOutput:(AVCaptureFileOutput *)captureOutput
|
137
|
+
didStartRecordingToOutputFileAtURL:(NSURL *)fileURL
|
138
|
+
fromConnections:(NSArray *)connections
|
139
|
+
{
|
140
|
+
if ([[self delegate] respondsToSelector:@selector(recorderRecordingDidBegin:)]) {
|
141
|
+
[[self delegate] recorderRecordingDidBegin:self];
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
- (void) captureOutput:(AVCaptureFileOutput *)captureOutput
|
146
|
+
didFinishRecordingToOutputFileAtURL:(NSURL *)anOutputFileURL
|
147
|
+
fromConnections:(NSArray *)connections
|
148
|
+
error:(NSError *)error
|
149
|
+
{
|
150
|
+
if ([[self delegate] respondsToSelector:@selector(recorder:recordingDidFinishToOutputFileURL:error:)]) {
|
151
|
+
[[self delegate] recorder:self recordingDidFinishToOutputFileURL:anOutputFileURL error:error];
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
@end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
/*
|
2
|
+
File: AVCamUtilities.h
|
3
|
+
Abstract: A utility class containing a method to find an AVCaptureConnection of a particular media type from an array of AVCaptureConnections.
|
4
|
+
Version: 1.2
|
5
|
+
|
6
|
+
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
|
7
|
+
Inc. ("Apple") in consideration of your agreement to the following
|
8
|
+
terms, and your use, installation, modification or redistribution of
|
9
|
+
this Apple software constitutes acceptance of these terms. If you do
|
10
|
+
not agree with these terms, please do not use, install, modify or
|
11
|
+
redistribute this Apple software.
|
12
|
+
|
13
|
+
In consideration of your agreement to abide by the following terms, and
|
14
|
+
subject to these terms, Apple grants you a personal, non-exclusive
|
15
|
+
license, under Apple's copyrights in this original Apple software (the
|
16
|
+
"Apple Software"), to use, reproduce, modify and redistribute the Apple
|
17
|
+
Software, with or without modifications, in source and/or binary forms;
|
18
|
+
provided that if you redistribute the Apple Software in its entirety and
|
19
|
+
without modifications, you must retain this notice and the following
|
20
|
+
text and disclaimers in all such redistributions of the Apple Software.
|
21
|
+
Neither the name, trademarks, service marks or logos of Apple Inc. may
|
22
|
+
be used to endorse or promote products derived from the Apple Software
|
23
|
+
without specific prior written permission from Apple. Except as
|
24
|
+
expressly stated in this notice, no other rights or licenses, express or
|
25
|
+
implied, are granted by Apple herein, including but not limited to any
|
26
|
+
patent rights that may be infringed by your derivative works or by other
|
27
|
+
works in which the Apple Software may be incorporated.
|
28
|
+
|
29
|
+
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
|
30
|
+
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
|
31
|
+
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
|
32
|
+
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
|
33
|
+
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
|
34
|
+
|
35
|
+
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
|
36
|
+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
37
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
38
|
+
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
|
39
|
+
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
|
40
|
+
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
|
41
|
+
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
|
42
|
+
POSSIBILITY OF SUCH DAMAGE.
|
43
|
+
|
44
|
+
Copyright (C) 2011 Apple Inc. All Rights Reserved.
|
45
|
+
|
46
|
+
*/
|
47
|
+
|
48
|
+
#import <Foundation/Foundation.h>
|
49
|
+
#import <UIKit/UIKit.h>
|
50
|
+
#import <AVFoundation/AVFoundation.h>
|
51
|
+
|
52
|
+
|
53
|
+
@class AVCaptureConnection;
|
54
|
+
|
55
|
+
@interface AVCamUtilities : NSObject {
|
56
|
+
|
57
|
+
}
|
58
|
+
|
59
|
+
+ (AVCaptureConnection *)connectionWithMediaType:(NSString *)mediaType fromConnections:(NSArray *)connections;
|
60
|
+
|
61
|
+
@end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
/*
|
2
|
+
File: AVCamUtilities.m
|
3
|
+
Abstract: A utility class containing a method to find an AVCaptureConnection of a particular media type from an array of AVCaptureConnections.
|
4
|
+
Version: 1.2
|
5
|
+
|
6
|
+
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
|
7
|
+
Inc. ("Apple") in consideration of your agreement to the following
|
8
|
+
terms, and your use, installation, modification or redistribution of
|
9
|
+
this Apple software constitutes acceptance of these terms. If you do
|
10
|
+
not agree with these terms, please do not use, install, modify or
|
11
|
+
redistribute this Apple software.
|
12
|
+
|
13
|
+
In consideration of your agreement to abide by the following terms, and
|
14
|
+
subject to these terms, Apple grants you a personal, non-exclusive
|
15
|
+
license, under Apple's copyrights in this original Apple software (the
|
16
|
+
"Apple Software"), to use, reproduce, modify and redistribute the Apple
|
17
|
+
Software, with or without modifications, in source and/or binary forms;
|
18
|
+
provided that if you redistribute the Apple Software in its entirety and
|
19
|
+
without modifications, you must retain this notice and the following
|
20
|
+
text and disclaimers in all such redistributions of the Apple Software.
|
21
|
+
Neither the name, trademarks, service marks or logos of Apple Inc. may
|
22
|
+
be used to endorse or promote products derived from the Apple Software
|
23
|
+
without specific prior written permission from Apple. Except as
|
24
|
+
expressly stated in this notice, no other rights or licenses, express or
|
25
|
+
implied, are granted by Apple herein, including but not limited to any
|
26
|
+
patent rights that may be infringed by your derivative works or by other
|
27
|
+
works in which the Apple Software may be incorporated.
|
28
|
+
|
29
|
+
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
|
30
|
+
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
|
31
|
+
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
|
32
|
+
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
|
33
|
+
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
|
34
|
+
|
35
|
+
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
|
36
|
+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
37
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
38
|
+
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
|
39
|
+
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
|
40
|
+
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
|
41
|
+
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
|
42
|
+
POSSIBILITY OF SUCH DAMAGE.
|
43
|
+
|
44
|
+
Copyright (C) 2011 Apple Inc. All Rights Reserved.
|
45
|
+
|
46
|
+
*/
|
47
|
+
|
48
|
+
#import "AVCamUtilities.h"
|
49
|
+
|
50
|
+
|
51
|
+
@implementation AVCamUtilities
|
52
|
+
|
53
|
+
+ (AVCaptureConnection *)connectionWithMediaType:(NSString *)mediaType fromConnections:(NSArray *)connections
|
54
|
+
{
|
55
|
+
for ( AVCaptureConnection *connection in connections ) {
|
56
|
+
for ( AVCaptureInputPort *port in [connection inputPorts] ) {
|
57
|
+
if ( [[port mediaType] isEqual:mediaType] ) {
|
58
|
+
return connection;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
return nil;
|
63
|
+
}
|
64
|
+
|
65
|
+
@end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
//
|
2
|
+
// AVClub.h
|
3
|
+
// AVClub
|
4
|
+
//
|
5
|
+
// Created by Colin Thomas-Arnold on 11/16/12.
|
6
|
+
// Copyright (c) 2012 colinta. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#import <Foundation/Foundation.h>
|
10
|
+
#import <UIKit/UIKit.h>
|
11
|
+
#import <AVFoundation/AVFoundation.h>
|
12
|
+
|
13
|
+
|
14
|
+
@class AVCamRecorder, AVCaptureVideoPreviewLayer;
|
15
|
+
@protocol AVClubDelegate;
|
16
|
+
|
17
|
+
|
18
|
+
@interface AVClub : NSObject
|
19
|
+
|
20
|
+
|
21
|
+
@property (nonatomic,retain) AVCaptureSession *session;
|
22
|
+
@property (nonatomic,assign) AVCaptureVideoOrientation orientation;
|
23
|
+
@property (nonatomic,retain) AVCaptureDeviceInput *videoInput;
|
24
|
+
@property (nonatomic,retain) AVCaptureDeviceInput *audioInput;
|
25
|
+
@property (nonatomic,retain) AVCaptureStillImageOutput *stillImageOutput;
|
26
|
+
@property (nonatomic,retain) AVCamRecorder *recorder;
|
27
|
+
@property (nonatomic,assign) id deviceConnectedObserver;
|
28
|
+
@property (nonatomic,assign) id deviceDisconnectedObserver;
|
29
|
+
@property (nonatomic,assign) UIBackgroundTaskIdentifier backgroundRecordingID;
|
30
|
+
@property (nonatomic,assign) id <AVClubDelegate> delegate;
|
31
|
+
@property (nonatomic,assign) UIView *viewFinderView;
|
32
|
+
@property (nonatomic,assign) BOOL isRunning;
|
33
|
+
@property (nonatomic,retain) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;
|
34
|
+
|
35
|
+
|
36
|
+
- (void) startInView:(UIView*)videoView;
|
37
|
+
- (void) startSession;
|
38
|
+
- (void) stopSession;
|
39
|
+
|
40
|
+
- (CGPoint)convertToPointOfInterestFromViewCoordinates:(CGPoint)viewCoordinates;
|
41
|
+
- (void) startRecording;
|
42
|
+
- (void) stopRecording;
|
43
|
+
- (void) saveImageToLibrary:(UIImage*)image;
|
44
|
+
- (void) captureStillImage;
|
45
|
+
- (void) captureStillImageAnimated:(BOOL)animated;
|
46
|
+
- (BOOL) toggleCamera;
|
47
|
+
- (BOOL) toggleCameraAnimated:(BOOL)animated;
|
48
|
+
- (void) autoFocusAtPoint:(CGPoint)point;
|
49
|
+
- (void) continuousFocusAtPoint:(CGPoint)point;
|
50
|
+
|
51
|
+
|
52
|
+
- (NSUInteger) cameraCount;
|
53
|
+
- (NSUInteger) micCount;
|
54
|
+
- (BOOL) hasCamera;
|
55
|
+
- (BOOL) hasMultipleCameras;
|
56
|
+
- (BOOL) hasAudio;
|
57
|
+
- (BOOL) hasVideo;
|
58
|
+
|
59
|
+
|
60
|
+
@end
|
61
|
+
|
62
|
+
|
63
|
+
// These delegate methods are always called on the main CFRunLoop
|
64
|
+
@protocol AVClubDelegate <NSObject>
|
65
|
+
@optional
|
66
|
+
- (void) club:(AVClub *)club didFailWithError:(NSError *)error;
|
67
|
+
- (void) clubRecordingBegan:(AVClub *)club;
|
68
|
+
- (void) clubRecordingFinished:(AVClub *)club;
|
69
|
+
- (void) club:(AVClub*)club stillImageCaptured:(UIImage*)image error:(NSError*)error;
|
70
|
+
- (void) club:(AVClub*)club assetSavedToURL:(NSURL*)assetURL error:(NSError*)error;
|
71
|
+
- (void) clubDeviceConfigurationChanged:(AVClub *)club;
|
72
|
+
@end
|
@@ -0,0 +1,673 @@
|
|
1
|
+
//
|
2
|
+
// AVClub.m
|
3
|
+
// AVClub
|
4
|
+
//
|
5
|
+
// Created by Colin Thomas-Arnold on 11/16/12.
|
6
|
+
// Copyright (c) 2012 colinta. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#import "AVClub.h"
|
10
|
+
#import "AVCamRecorder.h"
|
11
|
+
#import "AVCamUtilities.h"
|
12
|
+
#import <MobileCoreServices/UTCoreTypes.h>
|
13
|
+
#import <AssetsLibrary/AssetsLibrary.h>
|
14
|
+
#import <ImageIO/CGImageProperties.h>
|
15
|
+
|
16
|
+
|
17
|
+
@interface AVClub (RecorderDelegate) <AVCamRecorderDelegate>
|
18
|
+
@end
|
19
|
+
|
20
|
+
|
21
|
+
#pragma mark -
|
22
|
+
@interface AVClub (InternalUtilityMethods)
|
23
|
+
- (AVCaptureDevice *) cameraWithPosition:(AVCaptureDevicePosition)position;
|
24
|
+
- (AVCaptureDevice *) frontFacingCamera;
|
25
|
+
- (AVCaptureDevice *) backFacingCamera;
|
26
|
+
- (AVCaptureDevice *) audioDevice;
|
27
|
+
- (NSURL *) tempFileURL;
|
28
|
+
- (void) removeFile:(NSURL *)outputFileURL;
|
29
|
+
- (void) copyFileToDocuments:(NSURL *)fileURL;
|
30
|
+
@end
|
31
|
+
|
32
|
+
|
33
|
+
#pragma mark -
|
34
|
+
@implementation AVClub
|
35
|
+
|
36
|
+
|
37
|
+
@synthesize session = session;
|
38
|
+
@synthesize delegate;
|
39
|
+
|
40
|
+
|
41
|
+
- (id) init
|
42
|
+
{
|
43
|
+
self = [super init];
|
44
|
+
if (self != nil) {
|
45
|
+
self.isRunning = NO;
|
46
|
+
|
47
|
+
__block id weakSelf = self;
|
48
|
+
void (^deviceConnectedBlock)(NSNotification *) = ^(NSNotification *notification) {
|
49
|
+
AVCaptureDevice *device = [notification object];
|
50
|
+
|
51
|
+
BOOL sessionHasDeviceWithMatchingMediaType = NO;
|
52
|
+
NSString *deviceMediaType = nil;
|
53
|
+
if ( [device hasMediaType:AVMediaTypeAudio] )
|
54
|
+
deviceMediaType = AVMediaTypeAudio;
|
55
|
+
else if ( [device hasMediaType:AVMediaTypeVideo] )
|
56
|
+
deviceMediaType = AVMediaTypeVideo;
|
57
|
+
|
58
|
+
if ( deviceMediaType )
|
59
|
+
{
|
60
|
+
for (AVCaptureDeviceInput *input in [self.session inputs])
|
61
|
+
{
|
62
|
+
if ( [[input device] hasMediaType:deviceMediaType] )
|
63
|
+
{
|
64
|
+
sessionHasDeviceWithMatchingMediaType = YES;
|
65
|
+
break;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
if ( ! sessionHasDeviceWithMatchingMediaType )
|
70
|
+
{
|
71
|
+
NSError *error;
|
72
|
+
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
|
73
|
+
if ( [session canAddInput:input] )
|
74
|
+
[session addInput:input];
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
if ( [delegate respondsToSelector:@selector(clubDeviceConfigurationChanged:)] )
|
79
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate clubDeviceConfigurationChanged:self]; });
|
80
|
+
};
|
81
|
+
void (^deviceDisconnectedBlock)(NSNotification *) = ^(NSNotification *notification) {
|
82
|
+
AVCaptureDevice *device = [notification object];
|
83
|
+
|
84
|
+
if ( [device hasMediaType:AVMediaTypeAudio] ) {
|
85
|
+
[session removeInput:[weakSelf audioInput]];
|
86
|
+
[weakSelf setAudioInput:nil];
|
87
|
+
}
|
88
|
+
else if ( [device hasMediaType:AVMediaTypeVideo] ) {
|
89
|
+
[session removeInput:[weakSelf videoInput]];
|
90
|
+
[weakSelf setVideoInput:nil];
|
91
|
+
}
|
92
|
+
|
93
|
+
if ( [delegate respondsToSelector:@selector(clubDeviceConfigurationChanged:)] )
|
94
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate clubDeviceConfigurationChanged:self]; });
|
95
|
+
};
|
96
|
+
|
97
|
+
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
98
|
+
[self setDeviceConnectedObserver:[notificationCenter addObserverForName:AVCaptureDeviceWasConnectedNotification object:nil queue:nil usingBlock:deviceConnectedBlock]];
|
99
|
+
[self setDeviceDisconnectedObserver:[notificationCenter addObserverForName:AVCaptureDeviceWasDisconnectedNotification object:nil queue:nil usingBlock:deviceDisconnectedBlock]];
|
100
|
+
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
101
|
+
[notificationCenter addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil];
|
102
|
+
self.orientation = AVCaptureVideoOrientationPortrait;
|
103
|
+
}
|
104
|
+
|
105
|
+
return self;
|
106
|
+
}
|
107
|
+
|
108
|
+
- (void) dealloc
|
109
|
+
{
|
110
|
+
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
111
|
+
[notificationCenter removeObserver:[self deviceConnectedObserver]];
|
112
|
+
[notificationCenter removeObserver:[self deviceDisconnectedObserver]];
|
113
|
+
[notificationCenter removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
|
114
|
+
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
|
115
|
+
|
116
|
+
[self stopSession];
|
117
|
+
}
|
118
|
+
|
119
|
+
- (void) startInView:(UIView*)videoView
|
120
|
+
{
|
121
|
+
// Set torch and flash mode to auto
|
122
|
+
if ( [[self backFacingCamera] hasFlash] )
|
123
|
+
{
|
124
|
+
if ( [[self backFacingCamera] lockForConfiguration:nil] )
|
125
|
+
{
|
126
|
+
if ( [[self backFacingCamera] isFlashModeSupported:AVCaptureFlashModeAuto] )
|
127
|
+
[[self backFacingCamera] setFlashMode:AVCaptureFlashModeAuto];
|
128
|
+
|
129
|
+
[[self backFacingCamera] unlockForConfiguration];
|
130
|
+
}
|
131
|
+
}
|
132
|
+
if ( [[self backFacingCamera] hasTorch] )
|
133
|
+
{
|
134
|
+
if ( [[self backFacingCamera] lockForConfiguration:nil] )
|
135
|
+
{
|
136
|
+
if ( [[self backFacingCamera] isTorchModeSupported:AVCaptureTorchModeAuto] )
|
137
|
+
[[self backFacingCamera] setTorchMode:AVCaptureTorchModeAuto];
|
138
|
+
|
139
|
+
[[self backFacingCamera] unlockForConfiguration];
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
// Init the device inputs
|
144
|
+
AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backFacingCamera] error:nil];
|
145
|
+
AVCaptureDeviceInput *newAudioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self audioDevice] error:nil];
|
146
|
+
|
147
|
+
|
148
|
+
// Setup the still image file output
|
149
|
+
AVCaptureStillImageOutput *newStillImageOutput = [[AVCaptureStillImageOutput alloc] init];
|
150
|
+
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
|
151
|
+
AVVideoCodecJPEG, AVVideoCodecKey,
|
152
|
+
nil];
|
153
|
+
[newStillImageOutput setOutputSettings:outputSettings];
|
154
|
+
|
155
|
+
|
156
|
+
// Create session (use default AVCaptureSessionPresetHigh)
|
157
|
+
AVCaptureSession *newCaptureSession = [[AVCaptureSession alloc] init];
|
158
|
+
|
159
|
+
|
160
|
+
// Add inputs and output to the capture session
|
161
|
+
if ( [newCaptureSession canAddInput:newVideoInput] )
|
162
|
+
[newCaptureSession addInput:newVideoInput];
|
163
|
+
|
164
|
+
if ( [newCaptureSession canAddInput:newAudioInput] )
|
165
|
+
[newCaptureSession addInput:newAudioInput];
|
166
|
+
|
167
|
+
if ( [newCaptureSession canAddOutput:newStillImageOutput] )
|
168
|
+
[newCaptureSession addOutput:newStillImageOutput];
|
169
|
+
|
170
|
+
[self setStillImageOutput:newStillImageOutput];
|
171
|
+
[self setVideoInput:newVideoInput];
|
172
|
+
[self setAudioInput:newAudioInput];
|
173
|
+
[self setSession:newCaptureSession];
|
174
|
+
|
175
|
+
// Set up the movie file output
|
176
|
+
NSURL *outputFileURL = [self tempFileURL];
|
177
|
+
AVCamRecorder *newRecorder = [[AVCamRecorder alloc] initWithSession:[self session] outputFileURL:outputFileURL];
|
178
|
+
[newRecorder setDelegate:self];
|
179
|
+
|
180
|
+
// Send an error to the delegate if video recording is unavailable
|
181
|
+
if ( ! [newRecorder recordsVideo] && [newRecorder recordsAudio] )
|
182
|
+
{
|
183
|
+
NSString *localizedDescription = NSLocalizedString(@"Video recording unavailable", @"Video recording unavailable description");
|
184
|
+
NSString *localizedFailureReason = NSLocalizedString(@"Movies recorded on this device will only contain audio. They will be accessible through iTunes file sharing.", @"Video recording unavailable failure reason");
|
185
|
+
NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
|
186
|
+
localizedDescription, NSLocalizedDescriptionKey,
|
187
|
+
localizedFailureReason, NSLocalizedFailureReasonErrorKey,
|
188
|
+
nil];
|
189
|
+
NSError *noVideoError = [NSError errorWithDomain:@"AVCam" code:0 userInfo:errorDict];
|
190
|
+
if ( [delegate respondsToSelector:@selector(club:didFailWithError:)] )
|
191
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self didFailWithError:noVideoError]; });
|
192
|
+
}
|
193
|
+
|
194
|
+
[self setRecorder:newRecorder];
|
195
|
+
|
196
|
+
// Create video preview layer and add it to the UI
|
197
|
+
if ( videoView )
|
198
|
+
{
|
199
|
+
AVCaptureVideoPreviewLayer *newCaptureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:[self session]];
|
200
|
+
CALayer *viewLayer = [videoView layer];
|
201
|
+
[viewLayer setMasksToBounds:YES];
|
202
|
+
|
203
|
+
CGRect bounds = [videoView bounds];
|
204
|
+
[newCaptureVideoPreviewLayer setFrame:bounds];
|
205
|
+
|
206
|
+
if ( [[self recorder] isOrientationSupported] )
|
207
|
+
[[self recorder] setOrientation:AVCaptureVideoOrientationPortrait];
|
208
|
+
|
209
|
+
[newCaptureVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
|
210
|
+
|
211
|
+
[viewLayer insertSublayer:newCaptureVideoPreviewLayer below:[[viewLayer sublayers] objectAtIndex:0]];
|
212
|
+
|
213
|
+
self.captureVideoPreviewLayer = newCaptureVideoPreviewLayer;
|
214
|
+
|
215
|
+
// Start the session. This is done asychronously since -startRunning doesn't return until the session is running.
|
216
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
217
|
+
[self startSession];
|
218
|
+
});
|
219
|
+
|
220
|
+
[self setVideoPreviewView:videoView];
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
- (void) startSession
|
225
|
+
{
|
226
|
+
if ( ! self.isRunning )
|
227
|
+
{
|
228
|
+
[[self session] startRunning];
|
229
|
+
self.isRunning = YES;
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
- (void) stopSession
|
234
|
+
{
|
235
|
+
if ( self.isRunning )
|
236
|
+
{
|
237
|
+
[[self session] stopRunning];
|
238
|
+
self.isRunning = NO;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
- (void) startRecording
|
243
|
+
{
|
244
|
+
if ( [[UIDevice currentDevice] isMultitaskingSupported] )
|
245
|
+
{
|
246
|
+
// Setup background task. This is needed because the captureOutput:didFinishRecordingToOutputFileAtURL: callback is not received until AVCam returns
|
247
|
+
// to the foreground unless you request background execution time. This also ensures that there will be time to write the file to the assets library
|
248
|
+
// when AVCam is backgrounded. To conclude this background execution, -endBackgroundTask is called in -recorder:recordingDidFinishToOutputFileURL:error:
|
249
|
+
// after the recorded file has been saved.
|
250
|
+
[self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{}]];
|
251
|
+
}
|
252
|
+
|
253
|
+
[self removeFile:[[self recorder] outputFileURL]];
|
254
|
+
[[self recorder] startRecordingWithOrientation:self.orientation];
|
255
|
+
}
|
256
|
+
|
257
|
+
- (void) stopRecording
|
258
|
+
{
|
259
|
+
[[self recorder] stopRecording];
|
260
|
+
}
|
261
|
+
|
262
|
+
- (void) saveImageToLibrary:(UIImage*)image
|
263
|
+
{
|
264
|
+
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
|
265
|
+
|
266
|
+
ALAssetsLibraryWriteImageCompletionBlock completionBlock = ^(NSURL *assetURL, NSError *error) {
|
267
|
+
if ( [delegate respondsToSelector:@selector(club:assetSavedToURL:error:)] )
|
268
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self assetSavedToURL:assetURL error:error]; });
|
269
|
+
};
|
270
|
+
|
271
|
+
[library writeImageToSavedPhotosAlbum:[image CGImage]
|
272
|
+
orientation:(ALAssetOrientation)[image imageOrientation]
|
273
|
+
completionBlock:completionBlock];
|
274
|
+
}
|
275
|
+
|
276
|
+
- (void) captureStillImage
|
277
|
+
{
|
278
|
+
return [self captureStillImageAnimated:NO];
|
279
|
+
}
|
280
|
+
- (void) captureStillImageAnimated:(BOOL)animated
|
281
|
+
{
|
282
|
+
AVCaptureConnection *stillImageConnection = [AVCamUtilities connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self stillImageOutput] connections]];
|
283
|
+
if ( [stillImageConnection isVideoOrientationSupported] )
|
284
|
+
[stillImageConnection setVideoOrientation:self.orientation];
|
285
|
+
|
286
|
+
if ( animated )
|
287
|
+
{
|
288
|
+
// Flash the screen white and fade it out to give UI feedback that a still image was taken
|
289
|
+
UIView *flashView = [[UIView alloc] initWithFrame:[[[self viewFinderView] window] bounds]];
|
290
|
+
[flashView setBackgroundColor:[UIColor whiteColor]];
|
291
|
+
[[[self viewFinderView] window] addSubview:flashView];
|
292
|
+
|
293
|
+
[UIView animateWithDuration:.4f
|
294
|
+
animations:^{
|
295
|
+
[flashView setAlpha:0.f];
|
296
|
+
}
|
297
|
+
completion:^(BOOL finished){
|
298
|
+
[flashView removeFromSuperview];
|
299
|
+
}
|
300
|
+
];
|
301
|
+
}
|
302
|
+
|
303
|
+
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:stillImageConnection
|
304
|
+
completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
|
305
|
+
|
306
|
+
void (^completionBlock)(UIImage*,NSError*) = ^(UIImage *image, NSError *error) {
|
307
|
+
if ( error )
|
308
|
+
{
|
309
|
+
if ( [delegate respondsToSelector:@selector(club:stillImageCaptured:error:)] )
|
310
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self stillImageCaptured:nil error:error]; });
|
311
|
+
}
|
312
|
+
else if ( [delegate respondsToSelector:@selector(club:stillImageCaptured:error:)] )
|
313
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self stillImageCaptured:image error:nil]; });
|
314
|
+
};
|
315
|
+
|
316
|
+
if ( imageDataSampleBuffer )
|
317
|
+
{
|
318
|
+
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
|
319
|
+
|
320
|
+
UIImage *image = [[UIImage alloc] initWithData:imageData];
|
321
|
+
completionBlock(image, nil);
|
322
|
+
}
|
323
|
+
else
|
324
|
+
completionBlock(nil, error);
|
325
|
+
}];
|
326
|
+
}
|
327
|
+
|
328
|
+
// Toggle between the front and back camera, if both are present.
|
329
|
+
- (BOOL) toggleCamera
|
330
|
+
{
|
331
|
+
return [self toggleCameraAnimated:NO];
|
332
|
+
}
|
333
|
+
- (BOOL) toggleCameraAnimated:(BOOL)animated;
|
334
|
+
{
|
335
|
+
BOOL success = NO;
|
336
|
+
|
337
|
+
if ( [self cameraCount] > 1 )
|
338
|
+
{
|
339
|
+
NSError *error;
|
340
|
+
AVCaptureDeviceInput *newVideoInput;
|
341
|
+
AVCaptureDevicePosition position = [[self.videoInput device] position];
|
342
|
+
|
343
|
+
if ( position == AVCaptureDevicePositionBack )
|
344
|
+
newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontFacingCamera] error:&error];
|
345
|
+
else if ( position == AVCaptureDevicePositionFront )
|
346
|
+
newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backFacingCamera] error:&error];
|
347
|
+
else
|
348
|
+
goto bail;
|
349
|
+
|
350
|
+
if ( newVideoInput != nil )
|
351
|
+
{
|
352
|
+
[[self session] beginConfiguration];
|
353
|
+
[[self session] removeInput:[self videoInput]];
|
354
|
+
if ( [[self session] canAddInput:newVideoInput] )
|
355
|
+
{
|
356
|
+
[[self session] addInput:newVideoInput];
|
357
|
+
[self setVideoInput:newVideoInput];
|
358
|
+
}
|
359
|
+
else
|
360
|
+
[[self session] addInput:[self videoInput]];
|
361
|
+
[[self session] commitConfiguration];
|
362
|
+
success = YES;
|
363
|
+
}
|
364
|
+
else if ( error )
|
365
|
+
{
|
366
|
+
if ( [delegate respondsToSelector:@selector(club:didFailWithError:)] )
|
367
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self didFailWithError:error]; });
|
368
|
+
}
|
369
|
+
}
|
370
|
+
|
371
|
+
bail:
|
372
|
+
return success;
|
373
|
+
}
|
374
|
+
|
375
|
+
|
376
|
+
#pragma mark Device Counts
|
377
|
+
- (NSUInteger) cameraCount
|
378
|
+
{
|
379
|
+
return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
|
380
|
+
}
|
381
|
+
|
382
|
+
- (NSUInteger) micCount
|
383
|
+
{
|
384
|
+
return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] count];
|
385
|
+
}
|
386
|
+
|
387
|
+
- (BOOL) hasCamera
|
388
|
+
{
|
389
|
+
return [self cameraCount] > 0;
|
390
|
+
}
|
391
|
+
- (BOOL) hasMultipleCameras
|
392
|
+
{
|
393
|
+
return [self cameraCount] > 1;
|
394
|
+
}
|
395
|
+
- (BOOL) hasAudio
|
396
|
+
{
|
397
|
+
return [self micCount] > 0;
|
398
|
+
}
|
399
|
+
- (BOOL) hasVideo
|
400
|
+
{
|
401
|
+
return [self hasCamera] && [self hasAudio];
|
402
|
+
}
|
403
|
+
|
404
|
+
|
405
|
+
#pragma mark Camera Properties
|
406
|
+
// Perform an auto focus at the specified point. The focus mode will automatically change to locked once the auto focus is complete.
|
407
|
+
- (void) autoFocusAtPoint:(CGPoint)point
|
408
|
+
{
|
409
|
+
AVCaptureDevice *device = [[self videoInput] device];
|
410
|
+
if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
|
411
|
+
NSError *error;
|
412
|
+
if ( [device lockForConfiguration:&error] )
|
413
|
+
{
|
414
|
+
[device setFocusPointOfInterest:point];
|
415
|
+
[device setFocusMode:AVCaptureFocusModeAutoFocus];
|
416
|
+
[device unlockForConfiguration];
|
417
|
+
}
|
418
|
+
else
|
419
|
+
{
|
420
|
+
if ([delegate respondsToSelector:@selector(club:didFailWithError:)])
|
421
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self didFailWithError:error]; });
|
422
|
+
}
|
423
|
+
}
|
424
|
+
}
|
425
|
+
|
426
|
+
// Switch to continuous auto focus mode at the specified point
|
427
|
+
- (void) continuousFocusAtPoint:(CGPoint)point
|
428
|
+
{
|
429
|
+
AVCaptureDevice *device = [[self videoInput] device];
|
430
|
+
|
431
|
+
if ( [device isFocusPointOfInterestSupported] && [device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus] )
|
432
|
+
{
|
433
|
+
NSError *error;
|
434
|
+
if ( [device lockForConfiguration:&error] )
|
435
|
+
{
|
436
|
+
[device setFocusPointOfInterest:point];
|
437
|
+
[device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
|
438
|
+
[device unlockForConfiguration];
|
439
|
+
}
|
440
|
+
else
|
441
|
+
{
|
442
|
+
if ( [delegate respondsToSelector:@selector(club:didFailWithError:)] )
|
443
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self didFailWithError:error]; });
|
444
|
+
}
|
445
|
+
}
|
446
|
+
}
|
447
|
+
|
448
|
+
// Convert from view coordinates to camera coordinates, where {0,0} represents the top left of the picture area, and {1,1} represents
|
449
|
+
// the bottom right in landscape mode with the home button on the right.
|
450
|
+
- (CGPoint)convertToPointOfInterestFromViewCoordinates:(CGPoint)viewCoordinates
|
451
|
+
{
|
452
|
+
CGPoint pointOfInterest = CGPointMake(.5f, .5f);
|
453
|
+
CGSize frameSize = [[self viewFinderView] frame].size;
|
454
|
+
|
455
|
+
if ( [[self recorder] isMirrored] )
|
456
|
+
viewCoordinates.x = frameSize.width - viewCoordinates.x;
|
457
|
+
|
458
|
+
if ( [[self.captureVideoPreviewLayer videoGravity] isEqualToString:AVLayerVideoGravityResize] )
|
459
|
+
{
|
460
|
+
// Scale, switch x and y, and reverse x
|
461
|
+
pointOfInterest = CGPointMake(viewCoordinates.y / frameSize.height, 1.f - (viewCoordinates.x / frameSize.width));
|
462
|
+
}
|
463
|
+
else
|
464
|
+
{
|
465
|
+
CGRect cleanAperture;
|
466
|
+
for (AVCaptureInputPort *port in [[self videoInput] ports])
|
467
|
+
{
|
468
|
+
if ( [port mediaType] == AVMediaTypeVideo )
|
469
|
+
{
|
470
|
+
cleanAperture = CMVideoFormatDescriptionGetCleanAperture([port formatDescription], YES);
|
471
|
+
CGSize apertureSize = cleanAperture.size;
|
472
|
+
CGPoint point = viewCoordinates;
|
473
|
+
|
474
|
+
CGFloat apertureRatio = apertureSize.height / apertureSize.width;
|
475
|
+
CGFloat viewRatio = frameSize.width / frameSize.height;
|
476
|
+
CGFloat xc = .5f;
|
477
|
+
CGFloat yc = .5f;
|
478
|
+
|
479
|
+
if ( [[self.captureVideoPreviewLayer videoGravity] isEqualToString:AVLayerVideoGravityResizeAspect] )
|
480
|
+
{
|
481
|
+
if (viewRatio > apertureRatio)
|
482
|
+
{
|
483
|
+
CGFloat y2 = frameSize.height;
|
484
|
+
CGFloat x2 = frameSize.height * apertureRatio;
|
485
|
+
CGFloat x1 = frameSize.width;
|
486
|
+
CGFloat blackBar = (x1 - x2) / 2;
|
487
|
+
// If point is inside letterboxed area, do coordinate conversion; otherwise, don't change the default value returned (.5,.5)
|
488
|
+
if (point.x >= blackBar && point.x <= blackBar + x2) {
|
489
|
+
// Scale (accounting for the letterboxing on the left and right of the video preview), switch x and y, and reverse x
|
490
|
+
xc = point.y / y2;
|
491
|
+
yc = 1.f - ((point.x - blackBar) / x2);
|
492
|
+
}
|
493
|
+
}
|
494
|
+
else
|
495
|
+
{
|
496
|
+
CGFloat y2 = frameSize.width / apertureRatio;
|
497
|
+
CGFloat y1 = frameSize.height;
|
498
|
+
CGFloat x2 = frameSize.width;
|
499
|
+
CGFloat blackBar = (y1 - y2) / 2;
|
500
|
+
// If point is inside letterboxed area, do coordinate conversion. Otherwise, don't change the default value returned (.5,.5)
|
501
|
+
if (point.y >= blackBar && point.y <= blackBar + y2) {
|
502
|
+
// Scale (accounting for the letterboxing on the top and bottom of the video preview), switch x and y, and reverse x
|
503
|
+
xc = ((point.y - blackBar) / y2);
|
504
|
+
yc = 1.f - (point.x / x2);
|
505
|
+
}
|
506
|
+
}
|
507
|
+
}
|
508
|
+
else if ( [[self.captureVideoPreviewLayer videoGravity] isEqualToString:AVLayerVideoGravityResizeAspectFill] )
|
509
|
+
{
|
510
|
+
// Scale, switch x and y, and reverse x
|
511
|
+
if ( viewRatio > apertureRatio )
|
512
|
+
{
|
513
|
+
CGFloat y2 = apertureSize.width * (frameSize.width / apertureSize.height);
|
514
|
+
xc = (point.y + ((y2 - frameSize.height) / 2.f)) / y2; // Account for cropped height
|
515
|
+
yc = (frameSize.width - point.x) / frameSize.width;
|
516
|
+
}
|
517
|
+
else
|
518
|
+
{
|
519
|
+
CGFloat x2 = apertureSize.height * (frameSize.height / apertureSize.width);
|
520
|
+
yc = 1.f - ((point.x + ((x2 - frameSize.width) / 2)) / x2); // Account for cropped width
|
521
|
+
xc = point.y / frameSize.height;
|
522
|
+
}
|
523
|
+
}
|
524
|
+
|
525
|
+
pointOfInterest = CGPointMake(xc, yc);
|
526
|
+
break;
|
527
|
+
}
|
528
|
+
}
|
529
|
+
}
|
530
|
+
|
531
|
+
return pointOfInterest;
|
532
|
+
}
|
533
|
+
|
534
|
+
@end
|
535
|
+
|
536
|
+
|
537
|
+
#pragma mark -
|
538
|
+
@implementation AVClub (InternalUtilityMethods)
|
539
|
+
|
540
|
+
// Keep track of current device orientation so it can be applied to movie recordings and still image captures
|
541
|
+
- (void)deviceOrientationDidChange
|
542
|
+
{
|
543
|
+
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
|
544
|
+
|
545
|
+
if ( deviceOrientation == UIDeviceOrientationPortrait )
|
546
|
+
self.orientation = AVCaptureVideoOrientationPortrait;
|
547
|
+
else if ( deviceOrientation == UIDeviceOrientationPortraitUpsideDown )
|
548
|
+
self.orientation = AVCaptureVideoOrientationPortraitUpsideDown;
|
549
|
+
|
550
|
+
// AVCapture and UIDevice have opposite meanings for landscape left and right (AVCapture orientation is the same as UIInterfaceOrientation)
|
551
|
+
else if ( deviceOrientation == UIDeviceOrientationLandscapeLeft )
|
552
|
+
self.orientation = AVCaptureVideoOrientationLandscapeRight;
|
553
|
+
else if ( deviceOrientation == UIDeviceOrientationLandscapeRight )
|
554
|
+
self.orientation = AVCaptureVideoOrientationLandscapeLeft;
|
555
|
+
|
556
|
+
// Ignore device orientations for which there is no corresponding still image orientation (e.g. UIDeviceOrientationFaceUp)
|
557
|
+
}
|
558
|
+
|
559
|
+
// Find a camera with the specificed AVCaptureDevicePosition, returning nil if one is not found
|
560
|
+
- (AVCaptureDevice *) cameraWithPosition:(AVCaptureDevicePosition) position
|
561
|
+
{
|
562
|
+
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
563
|
+
for (AVCaptureDevice *device in devices)
|
564
|
+
{
|
565
|
+
if ( [device position] == position )
|
566
|
+
return device;
|
567
|
+
}
|
568
|
+
return nil;
|
569
|
+
}
|
570
|
+
|
571
|
+
// Find a front facing camera, returning nil if one is not found
|
572
|
+
- (AVCaptureDevice *) frontFacingCamera
|
573
|
+
{
|
574
|
+
return [self cameraWithPosition:AVCaptureDevicePositionFront];
|
575
|
+
}
|
576
|
+
|
577
|
+
// Find a back facing camera, returning nil if one is not found
|
578
|
+
- (AVCaptureDevice *) backFacingCamera
|
579
|
+
{
|
580
|
+
return [self cameraWithPosition:AVCaptureDevicePositionBack];
|
581
|
+
}
|
582
|
+
|
583
|
+
// Find and return an audio device, returning nil if one is not found
|
584
|
+
- (AVCaptureDevice *) audioDevice
|
585
|
+
{
|
586
|
+
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
|
587
|
+
if ( [devices count] > 0 )
|
588
|
+
return [devices objectAtIndex:0];
|
589
|
+
return nil;
|
590
|
+
}
|
591
|
+
|
592
|
+
- (NSURL *) tempFileURL
|
593
|
+
{
|
594
|
+
return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.mov"]];
|
595
|
+
}
|
596
|
+
|
597
|
+
- (void) removeFile:(NSURL *)fileURL
|
598
|
+
{
|
599
|
+
NSString *filePath = [fileURL path];
|
600
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
601
|
+
if ( [fileManager fileExistsAtPath:filePath] )
|
602
|
+
{
|
603
|
+
NSError *error;
|
604
|
+
if ( [fileManager removeItemAtPath:filePath error:&error] == NO )
|
605
|
+
{
|
606
|
+
if ( [delegate respondsToSelector:@selector(club:didFailWithError:)] )
|
607
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self didFailWithError:error]; });
|
608
|
+
}
|
609
|
+
}
|
610
|
+
}
|
611
|
+
|
612
|
+
- (void) copyFileToDocuments:(NSURL *)fileURL
|
613
|
+
{
|
614
|
+
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
615
|
+
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
616
|
+
[dateFormatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
|
617
|
+
NSString *destinationPath = [documentsDirectory stringByAppendingFormat:@"/output_%@.mov", [dateFormatter stringFromDate:[NSDate date]]];
|
618
|
+
NSError *error;
|
619
|
+
if ( ! [[NSFileManager defaultManager] copyItemAtURL:fileURL toURL:[NSURL fileURLWithPath:destinationPath] error:&error] )
|
620
|
+
{
|
621
|
+
if ( [delegate respondsToSelector:@selector(club:didFailWithError:)] )
|
622
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self didFailWithError:error]; });
|
623
|
+
}
|
624
|
+
}
|
625
|
+
|
626
|
+
@end
|
627
|
+
|
628
|
+
|
629
|
+
#pragma mark -
|
630
|
+
@implementation AVClub (RecorderDelegate)
|
631
|
+
|
632
|
+
-(void)recorderRecordingDidBegin:(AVCamRecorder *)recorder
|
633
|
+
{
|
634
|
+
if ( [delegate respondsToSelector:@selector(clubRecordingBegan:)] )
|
635
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate clubRecordingBegan:self]; });
|
636
|
+
}
|
637
|
+
|
638
|
+
-(void)recorder:(AVCamRecorder *)recorder recordingDidFinishToOutputFileURL:(NSURL *)outputFileURL error:(NSError *)error
|
639
|
+
{
|
640
|
+
if ( [[self recorder] recordsAudio] && ![[self recorder] recordsVideo] )
|
641
|
+
{
|
642
|
+
// If the file was created on a device that doesn't support video recording, it can't be saved to the assets
|
643
|
+
// library. Instead, save it in the app's Documents directory, whence it can be copied from the device via
|
644
|
+
// iTunes file sharing.
|
645
|
+
[self copyFileToDocuments:outputFileURL];
|
646
|
+
|
647
|
+
if ( [[UIDevice currentDevice] isMultitaskingSupported] )
|
648
|
+
[[UIApplication sharedApplication] endBackgroundTask:[self backgroundRecordingID]];
|
649
|
+
|
650
|
+
if ( [delegate respondsToSelector:@selector(clubRecordingFinished:)] )
|
651
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate clubRecordingFinished:self]; });
|
652
|
+
}
|
653
|
+
else
|
654
|
+
{
|
655
|
+
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
|
656
|
+
[library writeVideoAtPathToSavedPhotosAlbum:outputFileURL
|
657
|
+
completionBlock:^(NSURL *assetURL, NSError *error) {
|
658
|
+
if ( error )
|
659
|
+
{
|
660
|
+
if ([delegate respondsToSelector:@selector(club:didFailWithError:)])
|
661
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate club:self didFailWithError:error]; });
|
662
|
+
}
|
663
|
+
|
664
|
+
if ( [[UIDevice currentDevice] isMultitaskingSupported] )
|
665
|
+
[[UIApplication sharedApplication] endBackgroundTask:[self backgroundRecordingID]];
|
666
|
+
|
667
|
+
if ( [delegate respondsToSelector:@selector(clubRecordingFinished:)] )
|
668
|
+
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ [delegate clubRecordingFinished:self]; });
|
669
|
+
}];
|
670
|
+
}
|
671
|
+
}
|
672
|
+
|
673
|
+
@end
|