AVClub 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|