AVClub 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ .repl_history
2
+ build
3
+ tags
4
+ resources/*.nib
5
+ resources/*.momd
6
+ resources/*.storyboardc
7
+ .DS_Store
8
+ nbproject
9
+ .redcar
10
+ #*#
11
+ *~
12
+ *.sw[po]
13
+ .eprj
14
+ /.dat*
@@ -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
@@ -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
+ ```
@@ -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
@@ -0,0 +1,8 @@
1
+ class AppDelegate
2
+ def application(application, didFinishLaunchingWithOptions:launchOptions)
3
+ @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
4
+ @window.rootViewController = CameraController.new
5
+ @window.makeKeyAndVisible
6
+ true
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module AVClub
2
+ VERSION = '0.1.0'
3
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ describe "Application 'AVClub-demo'" do
2
+ before do
3
+ @app = UIApplication.sharedApplication
4
+ end
5
+
6
+ it "has one window" do
7
+ @app.windows.size.should == 1
8
+ end
9
+ end