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
data/.gitignore
ADDED
data/AVClub.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/AVClub/version.rb', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'AVClub'
|
6
|
+
gem.version = AVClub::VERSION
|
7
|
+
|
8
|
+
gem.authors = ['the rubymotion community']
|
9
|
+
|
10
|
+
gem.description = <<-DESC
|
11
|
+
Setup an AVCaptureSession with video, multiple cameras, and still image
|
12
|
+
capabilities. You can also easily have touch-to-focus and flash-on-picture
|
13
|
+
features.
|
14
|
+
DESC
|
15
|
+
|
16
|
+
gem.summary = 'A wrapper for AVFoundation to make it easy to implement a custom camera view.'
|
17
|
+
gem.homepage = 'https://github.com/rubymotion/AVClub'
|
18
|
+
|
19
|
+
gem.files = `git ls-files`.split($\)
|
20
|
+
gem.require_paths = ['lib']
|
21
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
22
|
+
|
23
|
+
gem.add_dependency 'rake'
|
24
|
+
gem.add_development_dependency 'rspec'
|
25
|
+
|
26
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
I've been pouring over the `AVCam` sample code from Apple, and I think I've
|
2
|
+
finally gotten my head around it enough to generalize it and, I hope, repackage
|
3
|
+
it for our benefit!
|
4
|
+
|
5
|
+
You can setup an AVCaptureSession with video, multiple cameras, and still image
|
6
|
+
capabilities. You can also easily have touch-to-focus and flash-on-picture
|
7
|
+
features. Just by implementing a few actions and delegate methods. I'm calling
|
8
|
+
it "AVClub".
|
9
|
+
|
10
|
+
Really, the code is largely unchanged from the sample code - if you've seen it,
|
11
|
+
I just moved more code into the Manager class, and renamed "AVCamManager" to
|
12
|
+
"AVClub", since that's the central "wrapper" class.
|
13
|
+
|
14
|
+
This tool - and AVFoundation in general - is much more low level than the
|
15
|
+
UIImagePickerController (see Camera Programming Topics for iOS). If you're
|
16
|
+
looking for an easy off-the-shelf solution, use BW::Camera or an instance of
|
17
|
+
UIImagePickerController.
|
18
|
+
|
19
|
+
Working with AVFoundation is like holding a dozen loose wires, plugging them all
|
20
|
+
into each other, and hoping that a photo or video comes out the end. If it goes
|
21
|
+
wrong, lemme know.
|
22
|
+
|
23
|
+
|
24
|
+
The basic process is this:
|
25
|
+
|
26
|
+
Create a view for where you want the camera to appear. Or don't, it's optional.
|
27
|
+
If you want to take a picture using the front camera with no preview, you can
|
28
|
+
do it! (creepy! :-P)
|
29
|
+
|
30
|
+
1. Create a "club" - `AVClub.new`.
|
31
|
+
2. Assign your controller as the delegate - `club.delegate = self`.
|
32
|
+
3. and when you're ready - `startInView(viewfinder_view)`. You can start and
|
33
|
+
stop the session by calling `club.stopSession`
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
def viewDidLoad
|
37
|
+
@video_view = UIView.alloc.initWithFrame([[10, 10], [100, 100]]) # an AVCaptureVideoPreviewLayer will be added to this view
|
38
|
+
club = AVClub.new
|
39
|
+
club.delegate = self
|
40
|
+
club.startInView(@video_view)
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
|
45
|
+
For convenience, there is an included `AVClubController` class that adds two
|
46
|
+
methods you can use or refer to:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# this method creates the club, assigns it to self.club, and assigns the
|
50
|
+
# viewFinderView that you pass to self.viewFinderView
|
51
|
+
def startInView(view)
|
52
|
+
|
53
|
+
# call this in willRotateToInterfaceOrientation(toInterfaceOrientation,
|
54
|
+
# duration:duration) and pass the new camera frame
|
55
|
+
def rotateCameraTo(new_frame, orientation:toInterfaceOrientation, duration:duration)
|
56
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
$:.unshift("/Library/RubyMotion/lib")
|
3
|
+
require 'motion/project'
|
4
|
+
|
5
|
+
|
6
|
+
Motion::Project::App.setup do |app|
|
7
|
+
# Use `rake config' to see complete project settings.
|
8
|
+
app.name = 'AVClub-Demo'
|
9
|
+
app.files.insert(0, 'lib/AVClub/AVClubController.rb')
|
10
|
+
# app.files_dependencies 'app/camera_controller.rb' => 'lib/AVClub/AVClubController.rb'
|
11
|
+
|
12
|
+
app.vendor_project('vendor/AVClub', :xcode)
|
13
|
+
# app.detect_dependencies = false
|
14
|
+
app.frameworks.concat [
|
15
|
+
'MediaPlayer',
|
16
|
+
'QuartzCore',
|
17
|
+
'CoreVideo',
|
18
|
+
'CoreMedia',
|
19
|
+
'AssetsLibrary',
|
20
|
+
'MobileCoreServices',
|
21
|
+
'AVFoundation',
|
22
|
+
]
|
23
|
+
end
|
data/app/app_delegate.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
class CameraController < AVClubController
|
2
|
+
attr_accessor :record_button, :still_button, :toggle_button;
|
3
|
+
|
4
|
+
def viewDidLoad
|
5
|
+
super
|
6
|
+
self.view.backgroundColor = UIColor.darkGrayColor
|
7
|
+
|
8
|
+
self.viewFinderView = UIView.alloc.initWithFrame(UIEdgeInsetsInsetRect(self.view.bounds, [50, 20, 20, 20]))
|
9
|
+
self.viewFinderView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
|
10
|
+
UIViewAutoresizingFlexibleWidth
|
11
|
+
self.viewFinderView.backgroundColor = UIColor.lightGrayColor
|
12
|
+
self.view.addSubview(self.viewFinderView)
|
13
|
+
|
14
|
+
### important ###
|
15
|
+
startInView(self.viewFinderView)
|
16
|
+
|
17
|
+
width = 0
|
18
|
+
height = 0
|
19
|
+
self.toggle_button = UIButton.buttonWithType(UIButtonTypeRoundedRect).tap do |button|
|
20
|
+
button.setTitle('Camera', forState:UIControlStateNormal)
|
21
|
+
button.sizeToFit
|
22
|
+
width += CGRectGetWidth(button.frame)
|
23
|
+
height = CGRectGetHeight(button.frame)
|
24
|
+
|
25
|
+
button.addTarget(self, action: 'toggleCamera:', forControlEvents:UIControlEventTouchUpInside)
|
26
|
+
end
|
27
|
+
width += 10
|
28
|
+
|
29
|
+
self.record_button = UIButton.buttonWithType(UIButtonTypeRoundedRect).tap do |button|
|
30
|
+
button.setTitle('Record', forState:UIControlStateNormal)
|
31
|
+
button.sizeToFit
|
32
|
+
width += CGRectGetWidth(button.frame)
|
33
|
+
|
34
|
+
button.addTarget(self, action: 'toggleRecording:', forControlEvents:UIControlEventTouchUpInside)
|
35
|
+
end
|
36
|
+
width += 10
|
37
|
+
|
38
|
+
self.still_button = UIButton.buttonWithType(UIButtonTypeRoundedRect).tap do |button|
|
39
|
+
button.setTitle('Photo', forState:UIControlStateNormal)
|
40
|
+
button.sizeToFit
|
41
|
+
width += CGRectGetWidth(button.frame)
|
42
|
+
|
43
|
+
button.addTarget(self, action: 'captureStillImage:', forControlEvents:UIControlEventTouchUpInside)
|
44
|
+
end
|
45
|
+
|
46
|
+
left = (CGRectGetWidth(self.view.frame) - width) / 2.0
|
47
|
+
top = 5
|
48
|
+
buttons_view = UIView.alloc.initWithFrame([[left, top], [width, height]])
|
49
|
+
buttons_view.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |
|
50
|
+
UIViewAutoresizingFlexibleTopMargin |
|
51
|
+
UIViewAutoresizingFlexibleRightMargin |
|
52
|
+
UIViewAutoresizingFlexibleBottomMargin
|
53
|
+
|
54
|
+
left = 0
|
55
|
+
top = 0
|
56
|
+
self.toggle_button.frame = [[left, top], self.toggle_button.frame.size]
|
57
|
+
left += CGRectGetWidth(self.toggle_button.frame) + 10
|
58
|
+
self.record_button.frame = [[left, top], self.record_button.frame.size]
|
59
|
+
left += CGRectGetWidth(self.record_button.frame) + 10
|
60
|
+
self.still_button.frame = [[left, top], self.still_button.frame.size]
|
61
|
+
left += CGRectGetWidth(self.still_button.frame) + 10
|
62
|
+
|
63
|
+
buttons_view.addSubview(self.toggle_button)
|
64
|
+
buttons_view.addSubview(self.record_button)
|
65
|
+
buttons_view.addSubview(self.still_button)
|
66
|
+
|
67
|
+
self.view.addSubview(buttons_view)
|
68
|
+
|
69
|
+
self.update_button_states
|
70
|
+
|
71
|
+
# Add a single tap gesture to focus on the point tapped, then lock focus
|
72
|
+
singleTap = UITapGestureRecognizer.alloc.initWithTarget(self, action:'tapToAutoFocus:')
|
73
|
+
singleTap.setDelegate(self)
|
74
|
+
singleTap.setNumberOfTapsRequired(1)
|
75
|
+
self.viewFinderView.addGestureRecognizer(singleTap)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Auto focus at a particular point. The focus mode will change to locked once
|
79
|
+
# the auto focus happens.
|
80
|
+
def tapToAutoFocus(gestureRecognizer)
|
81
|
+
return unless club.videoInput
|
82
|
+
|
83
|
+
if club.videoInput.device.isFocusPointOfInterestSupported
|
84
|
+
tapPoint = gestureRecognizer.locationInView(viewFinderView)
|
85
|
+
convertedFocusPoint = club.convertToPointOfInterestFromViewCoordinates(tapPoint)
|
86
|
+
club.autoFocusAtPoint(convertedFocusPoint)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Change to continuous auto focus. The camera will constantly focus at the
|
91
|
+
# point choosen.
|
92
|
+
def tapToContinouslyAutoFocus(gestureRecognizer)
|
93
|
+
return unless club.videoInput
|
94
|
+
|
95
|
+
if club.videoInput.device.isFocusPointOfInterestSupported
|
96
|
+
club.continuousFocusAtPoint(CGPoint.new(0.5, 0.5))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def toggleCamera(sender)
|
101
|
+
# Toggle between cameras when there is more than one
|
102
|
+
club.toggleCamera
|
103
|
+
|
104
|
+
# Do an initial focus
|
105
|
+
club.continuousFocusAtPoint(CGPoint.new(0.5, 0.5))
|
106
|
+
end
|
107
|
+
|
108
|
+
def toggleRecording(sender)
|
109
|
+
# Start recording if there isn't a recording running. Stop recording if there is.
|
110
|
+
record_button.setEnabled(false)
|
111
|
+
unless club.recorder.isRecording
|
112
|
+
club.startRecording
|
113
|
+
else
|
114
|
+
club.stopRecording
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def captureStillImage(sender)
|
119
|
+
return unless still_button.isEnabled
|
120
|
+
|
121
|
+
# Capture a still image
|
122
|
+
still_button.setEnabled(false)
|
123
|
+
club.captureStillImageAnimated(true)
|
124
|
+
end
|
125
|
+
|
126
|
+
def update_button_states
|
127
|
+
if club.cameraCount > 1
|
128
|
+
self.toggle_button.enabled = true
|
129
|
+
self.record_button.enabled = true
|
130
|
+
self.still_button.enabled = true
|
131
|
+
else
|
132
|
+
self.toggle_button.enabled = false
|
133
|
+
|
134
|
+
if club.cameraCount > 0
|
135
|
+
self.record_button.enabled = true
|
136
|
+
self.still_button.enabled = true
|
137
|
+
else
|
138
|
+
self.still_button.enabled = false
|
139
|
+
|
140
|
+
if club.micCount > 0
|
141
|
+
self.record_button = true
|
142
|
+
else
|
143
|
+
self.record_button = false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def clubRecordingBegan(club)
|
150
|
+
self.record_button.setTitle('Stop', forState:UIControlStateNormal)
|
151
|
+
update_button_states
|
152
|
+
end
|
153
|
+
|
154
|
+
def clubRecordingFinished(club)
|
155
|
+
self.record_button.setTitle('Record', forState:UIControlStateNormal)
|
156
|
+
update_button_states
|
157
|
+
end
|
158
|
+
|
159
|
+
def club(club, stillImageCaptured:image, error:error)
|
160
|
+
if image
|
161
|
+
club.saveImageToLibrary(image)
|
162
|
+
else
|
163
|
+
update_button_states
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def club(club, assetSavedToURL:image, error:error)
|
168
|
+
update_button_states
|
169
|
+
end
|
170
|
+
|
171
|
+
def clubDeviceConfigurationChanged(club)
|
172
|
+
update_button_states
|
173
|
+
end
|
174
|
+
|
175
|
+
def willRotateToInterfaceOrientation(toInterfaceOrientation, duration:duration)
|
176
|
+
super
|
177
|
+
|
178
|
+
case toInterfaceOrientation
|
179
|
+
when UIInterfaceOrientationLandscapeLeft
|
180
|
+
new_frame = CGRect.new([0, 0], [480, 320])
|
181
|
+
when UIInterfaceOrientationLandscapeRight
|
182
|
+
new_frame = CGRect.new([0, 0], [480, 320])
|
183
|
+
when UIInterfaceOrientationPortrait
|
184
|
+
new_frame = CGRect.new([0, 0], [320, 480])
|
185
|
+
when UIInterfaceOrientationPortraitUpsideDown
|
186
|
+
new_frame = CGRect.new([0, 0], [320, 480])
|
187
|
+
end
|
188
|
+
|
189
|
+
### important ###
|
190
|
+
rotateCameraTo(new_frame, orientation:toInterfaceOrientation, duration:duration)
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class AVClubController < UIViewController
|
2
|
+
attr_accessor :club
|
3
|
+
attr_accessor :viewFinderView
|
4
|
+
|
5
|
+
def startInView(view)
|
6
|
+
self.viewFinderView = view
|
7
|
+
|
8
|
+
unless club
|
9
|
+
self.club = AVClub.new
|
10
|
+
self.club.delegate = self
|
11
|
+
self.club.startInView(view)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def club(club, didFailWithError:error)
|
16
|
+
alertView = UIAlertView.alloc.initWithTitle(error.localizedDescription,
|
17
|
+
message:error.localizedFailureReason,
|
18
|
+
delegate:nil,
|
19
|
+
cancelButtonTitle:'OK',
|
20
|
+
otherButtonTitles:nil
|
21
|
+
)
|
22
|
+
alertView.show
|
23
|
+
end
|
24
|
+
|
25
|
+
def club(club, stillImageCaptured:image, error:error)
|
26
|
+
end
|
27
|
+
|
28
|
+
def club(club, assetSavedToURL:url, error:error)
|
29
|
+
end
|
30
|
+
|
31
|
+
def clubRecordingBegan(club)
|
32
|
+
end
|
33
|
+
|
34
|
+
def clubRecordingFinished(club)
|
35
|
+
end
|
36
|
+
|
37
|
+
def clubDeviceConfigurationChanged(club)
|
38
|
+
end
|
39
|
+
|
40
|
+
def rotateCameraTo(new_frame, orientation:toInterfaceOrientation, duration:duration)
|
41
|
+
return unless viewFinderView
|
42
|
+
|
43
|
+
captureVideoPreviewLayer = nil
|
44
|
+
viewFinderView.layer.sublayers.each do |layer|
|
45
|
+
if layer.is_a? AVCaptureVideoPreviewLayer
|
46
|
+
captureVideoPreviewLayer = layer
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return unless captureVideoPreviewLayer
|
51
|
+
|
52
|
+
case toInterfaceOrientation
|
53
|
+
when UIInterfaceOrientationLandscapeLeft
|
54
|
+
rotation = Math::PI / 2
|
55
|
+
when UIInterfaceOrientationLandscapeRight
|
56
|
+
rotation = -Math::PI / 2
|
57
|
+
when UIInterfaceOrientationPortrait
|
58
|
+
rotation = 0
|
59
|
+
when UIInterfaceOrientationPortraitUpsideDown
|
60
|
+
rotation = 2 * Math::PI
|
61
|
+
end
|
62
|
+
|
63
|
+
captureVideoPreviewLayer.masksToBounds = true
|
64
|
+
UIView.animateWithDuration(duration, animations:lambda{
|
65
|
+
captureVideoPreviewLayer.frame = new_frame
|
66
|
+
captureVideoPreviewLayer.orientation = toInterfaceOrientation
|
67
|
+
})
|
68
|
+
end
|
69
|
+
end
|
data/lib/avclub.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "The AVClub gem must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
|
6
|
+
Motion::Project::App.setup do |app|
|
7
|
+
# scans app.files until it finds app/ (the default)
|
8
|
+
# if found, it inserts just before those files, otherwise it will insert to
|
9
|
+
# the end of the list
|
10
|
+
insert_point = 0
|
11
|
+
app.files.each_index do |index|
|
12
|
+
file = app.files[index]
|
13
|
+
if file =~ /^(?:\.\/)?app\//
|
14
|
+
# found app/, so stop looking
|
15
|
+
break
|
16
|
+
end
|
17
|
+
insert_point = index + 1
|
18
|
+
end
|
19
|
+
|
20
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'AVClub/**/*.rb')).reverse.each do |file|
|
21
|
+
app.files.insert(insert_point, file)
|
22
|
+
end
|
23
|
+
|
24
|
+
app.vendor_project(File.join(File.dirname(__FILE__), '../vendor/AVClub'), :xcode)
|
25
|
+
|
26
|
+
app.frameworks.concat [
|
27
|
+
'MediaPlayer',
|
28
|
+
'QuartzCore',
|
29
|
+
'CoreVideo',
|
30
|
+
'CoreMedia',
|
31
|
+
'AssetsLibrary',
|
32
|
+
'MobileCoreServices',
|
33
|
+
'AVFoundation',
|
34
|
+
]
|
35
|
+
end
|