├── resources ├── capture.png ├── flash-on.png ├── flash-off.png ├── toggle-camera.png ├── Default-568h@2x.png ├── capture-highlight.png └── toggle-camera-highlight.png ├── .gitignore ├── lib ├── motion-capture.rb └── project │ └── motion-capture.rb ├── app ├── app_delegate.rb └── controllers │ └── view_controller.rb ├── Rakefile ├── motion-capture.gemspec ├── LICENSE └── README.md /resources/capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblandin/motion-capture/HEAD/resources/capture.png -------------------------------------------------------------------------------- /resources/flash-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblandin/motion-capture/HEAD/resources/flash-on.png -------------------------------------------------------------------------------- /resources/flash-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblandin/motion-capture/HEAD/resources/flash-off.png -------------------------------------------------------------------------------- /resources/toggle-camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblandin/motion-capture/HEAD/resources/toggle-camera.png -------------------------------------------------------------------------------- /resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblandin/motion-capture/HEAD/resources/Default-568h@2x.png -------------------------------------------------------------------------------- /resources/capture-highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblandin/motion-capture/HEAD/resources/capture-highlight.png -------------------------------------------------------------------------------- /resources/toggle-camera-highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dblandin/motion-capture/HEAD/resources/toggle-camera-highlight.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .repl_history 2 | build 3 | tags 4 | app/pixate_code.rb 5 | resources/*.nib 6 | resources/*.momd 7 | resources/*.storyboardc 8 | .DS_Store 9 | nbproject 10 | .redcar 11 | #*# 12 | *~ 13 | *.sw[po] 14 | .eprj 15 | .sass-cache 16 | .idea 17 | -------------------------------------------------------------------------------- /lib/motion-capture.rb: -------------------------------------------------------------------------------- 1 | unless defined?(Motion::Project::Config) 2 | raise "This file must be required within a RubyMotion project Rakefile." 3 | end 4 | 5 | lib_dir_path = File.dirname(File.expand_path(__FILE__)) 6 | Motion::Project::App.setup do |app| 7 | app.files.unshift(Dir.glob(File.join(lib_dir_path, "project/**/*.rb"))) 8 | end 9 | -------------------------------------------------------------------------------- /app/app_delegate.rb: -------------------------------------------------------------------------------- 1 | class AppDelegate 2 | attr_accessor :window 3 | 4 | def application(application, didFinishLaunchingWithOptions: launch_options) 5 | return true if RUBYMOTION_ENV == 'test' 6 | 7 | initialize_main_controller 8 | 9 | true 10 | end 11 | 12 | private 13 | 14 | def initialize_main_controller 15 | self.window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 16 | 17 | window.setRootViewController(ViewController.alloc.init) 18 | 19 | window.makeKeyAndVisible 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | $:.unshift("/Library/RubyMotion/lib") 3 | require 'motion/project/template/ios' 4 | require './lib/motion-capture' 5 | 6 | Motion::Project::App.setup do |app| 7 | app.name = 'motion-capture' 8 | 9 | app.interface_orientations = [:portrait] 10 | 11 | app.frameworks += %w(AVFoundation Photos) 12 | 13 | app.info_plist['NSCameraUsageDescription'] = 'Camera will be used to display a preview and take a photo.' 14 | app.info_plist['NSPhotoLibraryUsageDescription'] = 'Photos taken will be saved to your library.' 15 | 16 | app.codesign_certificate = ENV['DEVELOPMENT_CERTIFICATE'] 17 | app.provisioning_profile = ENV['PROVISIONING_PROFILE'] 18 | end 19 | -------------------------------------------------------------------------------- /motion-capture.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | Gem::Specification.new do |spec| 3 | spec.name = 'motion-capture' 4 | spec.version = '1.4.0' 5 | spec.authors = ['Devon Blandin'] 6 | spec.email = ['dblandin@gmail.com'] 7 | spec.summary = %q{RubyMotion AVFoundation wrapper} 8 | spec.description = %q{Easily create custom camera controllers} 9 | spec.homepage = 'https://github.com/dblandin/motion-capture' 10 | spec.license = 'MIT' 11 | 12 | files = [] 13 | files << 'README.md' 14 | files.concat(Dir.glob('lib/**/*.rb')) 15 | spec.files = files 16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ['lib'] 19 | 20 | spec.add_development_dependency 'rake' 21 | end 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dscout, Inc 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # motion-capture 2 | 3 | Camera support for custom camera controllers 4 | 5 | ## Usage 6 | 7 | ``` ruby 8 | motion_capture = Motion::Capture.new 9 | motion_capture = Motion::Capture.new(device: :front) # specify camera 10 | motion_capture = Motion::Capture.new(preset: AVCaptureSessionPreset640x480) # specify a different preset (defaults to high resolution photo) 11 | 12 | motion_capture.attach(view) # apply a AVCaptureVideoPreviewLayer to the specified view 13 | 14 | motion_capture.toggle_camera # Switch between front/rear cameras 15 | motion_capture.toggle_flash # Switch bettwen flash on/off 16 | 17 | motion_capture.turn_flash_on 18 | motion_capture.turn_flash_off 19 | 20 | motion_capture.use_camera(:default) 21 | motion_capture.use_camera(:front) 22 | motion_capture.use_camera(:rear) 23 | 24 | # When you're ready to start the capture session: 25 | motion_capture.start! 26 | 27 | # Capturing Single Photos 28 | 29 | motion_capture.capture do |image_data| 30 | # Use NSData 31 | end 32 | 33 | motion_capture.capture_image do |image| 34 | # Use UIImage 35 | end 36 | 37 | # Saving captured images to the Photos library 38 | 39 | motion_capture.capture_and_save do |image_data, asset_url| 40 | # Use NSData and NSURL 41 | end 42 | 43 | motion_capture.capture_image_and_save do |image, asset_url| 44 | # Use UIImage and NSURL 45 | end 46 | 47 | # When you're done using the camera and are ready to stop the capture session: 48 | motion_capture.stop! 49 | ``` 50 | 51 | 52 | ## Setup 53 | 54 | Add this line to your application's Gemfile: 55 | 56 | gem 'motion-capture' 57 | 58 | And then execute: 59 | 60 | $ bundle 61 | 62 | Or install it yourself as: 63 | 64 | $ gem install motion-capture 65 | 66 | Add the necessary frameworks to your app configuration in your Rakefile: 67 | 68 | app.frameworks << 'AVFoundation' 69 | app.frameworks << 'Photos' # if you will be saving to the Photo library and targeting iOS 8+ 70 | app.frameworks << 'AssetsLibrary' # if you will be targeting iOS 4-7 71 | 72 | Then update your app configuration in your Rakefile to specify the message that will be displayed when asking the user for permission to use the camera: 73 | 74 | app.info_plist['NSCameraUsageDescription'] = 'Camera will be used for taking your profile photo.' 75 | 76 | If you will be saving photos to the Photo Library, you will also need to specify the message that will be displayed to the user: 77 | 78 | app.info_plist['NSPhotoLibraryUsageDescription'] = 'Photos taken will be saved to your library.' 79 | 80 | ## Contributing 81 | 82 | 1. Fork it 83 | 2. Create your feature branch (`git checkout -b my-new-feature`) 84 | 3. Commit your changes (`git commit -am 'Add some feature'`) 85 | 4. Push to the branch (`git push origin my-new-feature`) 86 | 5. Create new Pull Request 87 | -------------------------------------------------------------------------------- /app/controllers/view_controller.rb: -------------------------------------------------------------------------------- 1 | class ViewController < UIViewController 2 | def viewDidLoad 3 | super 4 | 5 | view.addSubview(flash_control_button) 6 | view.addSubview(camera_toggle_button) 7 | view.addSubview(capture_button) 8 | 9 | motion_capture.attach(view) 10 | 11 | motion_capture.start! 12 | end 13 | 14 | def capture(sender) 15 | motion_capture.capture_image_and_save do |image, asset_url| 16 | image_view.image = image 17 | 18 | view.addSubview(image_view) 19 | view.addSubview(reset_button) 20 | end 21 | end 22 | 23 | def reset(sender) 24 | reset_button.removeFromSuperview 25 | image_view.removeFromSuperview 26 | end 27 | 28 | def toggle_camera(sender) 29 | motion_capture.toggle_camera 30 | end 31 | 32 | def toggle_flash(sender) 33 | sender.selected = !sender.isSelected 34 | 35 | motion_capture.toggle_flash 36 | end 37 | 38 | def motion_capture 39 | @motion_capture ||= Motion::Capture.new(device: :rear) 40 | end 41 | 42 | def image_view 43 | @image_view ||= UIImageView.alloc.initWithFrame(view.bounds).tap do |image_view| 44 | image_view.contentMode = UIViewContentModeScaleAspectFill 45 | end 46 | end 47 | 48 | def camera_toggle_button 49 | UIButton.buttonWithType(UIButtonTypeCustom).tap do |button| 50 | x, y, w, h = 110, 0, 100, 100 51 | image_name = 'toggle-camera' 52 | action_selector = 'toggle_camera:' 53 | 54 | button.frame = [[x, y], [w, h]] 55 | button.setImage(UIImage.imageNamed(image_name), forState: UIControlStateNormal) 56 | button.setImage(UIImage.imageNamed("#{image_name}-highlight"), forState: UIControlStateHighlighted) 57 | button.addTarget(self, action: action_selector, forControlEvents: UIControlEventTouchUpInside) 58 | button.adjustsImageWhenHighlighted = false 59 | end 60 | end 61 | 62 | def capture_button 63 | UIButton.buttonWithType(UIButtonTypeCustom).tap do |button| 64 | button.size = CGSizeMake(100, 100) 65 | button.center = CGPointMake(view.size.width / 2, view.size.height - 100) 66 | button.setImage(UIImage.imageNamed('capture'), forState: UIControlStateNormal) 67 | button.setImage(UIImage.imageNamed('capture-highlight'), forState: UIControlStateHighlighted) 68 | button.adjustsImageWhenHighlighted = false 69 | button.addTarget(self, action: 'capture:', forControlEvents: UIControlEventTouchUpInside) 70 | end 71 | end 72 | 73 | def reset_button 74 | @reset_button ||= UIButton.buttonWithType(UIButtonTypeCustom).tap do |button| 75 | button.size = CGSizeMake(100, 100) 76 | button.center = CGPointMake(view.size.width / 2, view.size.height - 100) 77 | button.setImage(UIImage.imageNamed('capture'), forState: UIControlStateNormal) 78 | button.setImage(UIImage.imageNamed('capture-highlight'), forState: UIControlStateHighlighted) 79 | button.adjustsImageWhenHighlighted = false 80 | button.addTarget(self, action: 'reset:', forControlEvents: UIControlEventTouchUpInside) 81 | end 82 | end 83 | 84 | def flash_control_button 85 | UIButton.buttonWithType(UIButtonTypeCustom).tap do |button| 86 | button.frame = [[0, 0], [100, 100]] 87 | button.setImage(UIImage.imageNamed('flash-off'), forState: UIControlStateNormal) 88 | button.setImage(UIImage.imageNamed('flash-on'), forState: UIControlStateHighlighted) 89 | button.setImage(UIImage.imageNamed('flash-on'), forState: UIControlStateSelected) 90 | button.adjustsImageWhenHighlighted = false 91 | button.addTarget(self, action: 'toggle_flash:', forControlEvents: UIControlEventTouchUpInside) 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/project/motion-capture.rb: -------------------------------------------------------------------------------- 1 | module Motion; class Capture 2 | CAMERA_POSITIONS = { rear: AVCaptureDevicePositionBack, front: AVCaptureDevicePositionFront } 3 | FLASH_MODES = { on: AVCaptureFlashModeOn, off: AVCaptureFlashModeOff, auto: AVCaptureFlashModeAuto } 4 | 5 | attr_reader :options, :device, :preview_layer 6 | 7 | def initialize(options = {}) 8 | @options = options 9 | end 10 | 11 | def on_error(&block) 12 | @error_callback = block 13 | end 14 | 15 | def start! 16 | return if session.running? 17 | if defined?(AVCapturePhotoOutput) # iOS 10+ 18 | @starting = true 19 | authorize_camera do |success| 20 | if success 21 | Dispatch::Queue.new('motion-capture').async do 22 | configure_session 23 | session.startRunning 24 | @starting = false 25 | end 26 | else 27 | @starting = false 28 | end 29 | end 30 | else # iOS 4-9 31 | configure_session 32 | session.startRunning 33 | end 34 | end 35 | 36 | def authorize_camera(&block) 37 | AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: -> (success) { 38 | block.call(success) 39 | }) 40 | end 41 | 42 | def configure_session 43 | session.beginConfiguration 44 | 45 | set_preset(options.fetch(:preset, AVCaptureSessionPresetPhoto)) 46 | 47 | use_camera(options.fetch(:device, :default)) 48 | 49 | if defined?(AVCapturePhotoOutput) # iOS 10+ 50 | add_output(photo_output) 51 | else # iOS 4-9 52 | add_output(still_image_output) 53 | end 54 | 55 | session.commitConfiguration 56 | end 57 | 58 | def running? 59 | @session && session.running? 60 | end 61 | 62 | def stop! 63 | session.stopRunning 64 | 65 | remove_outputs 66 | remove_inputs 67 | 68 | preview_layer.removeFromSuperlayer if preview_layer && preview_layer.superlayer 69 | 70 | @still_image_output = nil 71 | @photo_output = nil 72 | @session = nil 73 | @preview_layer = nil 74 | end 75 | 76 | def capture(&block) 77 | if defined?(AVCapturePhotoOutput) # iOS 10+ 78 | Dispatch::Queue.new('motion-capture').async do 79 | ensure_running_session do 80 | update_video_orientation! 81 | @capture_callback = block 82 | capture_settings = AVCapturePhotoSettings.photoSettingsWithFormat(AVVideoCodecKey => AVVideoCodecJPEG) 83 | photo_output.capturePhotoWithSettings(capture_settings, delegate: self) 84 | end 85 | end 86 | else # iOS 4-9 87 | still_image_output.captureStillImageAsynchronouslyFromConnection(still_image_connection, completionHandler: -> (buffer, error) { 88 | if error 89 | error_callback.call(error) 90 | else 91 | image_data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer) 92 | block.call(image_data) 93 | end 94 | }) 95 | end 96 | end 97 | 98 | def ensure_running_session(&block) 99 | start! unless @starting || session.running? 100 | while @starting || !session.running? 101 | # wait for session to start... 102 | end 103 | block.call 104 | end 105 | 106 | # iOS 11+ AVCapturePhotoCaptureDelegate method 107 | def captureOutput(output, didFinishProcessingPhoto: photo, error: error) 108 | if error 109 | error_callback.call(error) 110 | else 111 | @capture_callback.call(photo.fileDataRepresentation) 112 | end 113 | end 114 | 115 | # iOS 10 AVCapturePhotoCaptureDelegate method 116 | def captureOutput(output, didFinishProcessingPhotoSampleBuffer: photo_sample_buffer, previewPhotoSampleBuffer: preview_photo_sample_buffer, resolvedSettings: resolved_settings, bracketSettings: bracket_settings, error: error) 117 | if error 118 | error_callback.call(error) 119 | else 120 | jpeg_data = AVCapturePhotoOutput.jpegPhotoDataRepresentation( 121 | forJPEGSampleBuffer: photo_sample_buffer, 122 | previewPhotoSampleBuffer: preview_photo_sample_buffer 123 | ) 124 | @capture_callback.call(jpeg_data) 125 | end 126 | end 127 | 128 | def capture_image(&block) 129 | capture do |jpeg_data| 130 | image = UIImage.imageWithData(jpeg_data) 131 | block.call(image) 132 | end 133 | end 134 | 135 | def capture_and_save(&block) 136 | capture do |jpeg_data| 137 | save_data(jpeg_data) do |asset_url| 138 | block.call(jpeg_data, asset_url) 139 | end 140 | end 141 | end 142 | 143 | def capture_image_and_save(&block) 144 | capture do |jpeg_data| 145 | save_data(jpeg_data) do |asset_url| 146 | image = UIImage.imageWithData(jpeg_data) 147 | block.call(image, asset_url) 148 | end 149 | end 150 | end 151 | 152 | def save_data(jpeg_data, &block) 153 | if defined?(PHPhotoLibrary) # iOS 8+ 154 | save_to_photo_library(jpeg_data, &block) 155 | else # iOS 4-8 156 | save_to_assets_library(jpeg_data, &block) 157 | end 158 | end 159 | 160 | # iOS 4-8 161 | def save_to_assets_library(jpeg_data, &block) 162 | assets_library.writeImageDataToSavedPhotosAlbum(jpeg_data, metadata: nil, completionBlock: -> (asset_url, error) { 163 | error ? error_callback.call(error) : block.call(asset_url) 164 | }) 165 | end 166 | 167 | # iOS 8+ 168 | def save_to_photo_library(jpeg_data, &block) 169 | photo_library.performChanges(-> { 170 | image = UIImage.imageWithData(jpeg_data) 171 | PHAssetChangeRequest.creationRequestForAssetFromImage(image) 172 | }, completionHandler: -> (success, error) { 173 | if error 174 | error_callback.call(error) 175 | else 176 | block.call(nil) # asset url is not returned in completion block 177 | end 178 | }) 179 | end 180 | 181 | def attach(view, options = {}) 182 | @preview_layer = preview_layer_for_view(view, options) 183 | 184 | view.layer.addSublayer(preview_layer) 185 | end 186 | 187 | def use_camera(target_camera = :default) 188 | @device = camera_devices[target_camera] 189 | 190 | error_pointer = Pointer.new(:object) 191 | 192 | if input = AVCaptureDeviceInput.deviceInputWithDevice(device, error: error_pointer) 193 | set_input(input) 194 | else 195 | error_callback.call(error_pointer[0]) 196 | end 197 | end 198 | 199 | def toggle_camera 200 | target_camera = using_rear_camera? ? :front : :rear 201 | 202 | use_camera(target_camera) 203 | end 204 | 205 | def toggle_flash 206 | if device && device.hasFlash 207 | target_mode = flash_on? ? :off : :on 208 | 209 | set_flash(target_mode) 210 | end 211 | end 212 | 213 | def can_set_preset?(preset) 214 | session.canSetSessionPreset(preset) 215 | end 216 | 217 | def set_preset(preset) 218 | session.sessionPreset = preset if can_set_preset? preset 219 | end 220 | 221 | def preset 222 | session.sessionPreset if @session 223 | end 224 | 225 | def flash 226 | device.flashMode if @device 227 | end 228 | 229 | # iOS 4-9 230 | def set_flash(mode = :auto) 231 | configure_with_lock { device.flashMode = FLASH_MODES[mode] } if flash_mode_available?(mode) 232 | end 233 | 234 | def flash_mode_available?(mode) 235 | FLASH_MODES.keys.include?(mode) && device.isFlashModeSupported(FLASH_MODES[mode]) 236 | end 237 | 238 | private 239 | 240 | def error_callback 241 | @error_callback ||= -> (error) { p "An error occurred: #{error.localizedDescription}." } 242 | end 243 | 244 | # iOS 4-9 245 | def still_image_connection 246 | still_image_output.connectionWithMediaType(AVMediaTypeVideo).tap do |connection| 247 | device_orientation = UIDevice.currentDevice.orientation 248 | video_orientation = orientation_mapping.fetch(device_orientation, AVCaptureVideoOrientationPortrait) 249 | 250 | connection.setVideoOrientation(video_orientation) if connection.videoOrientationSupported? 251 | end 252 | end 253 | 254 | # iOS 10+ 255 | def update_video_orientation! 256 | photo_output.connectionWithMediaType(AVMediaTypeVideo).tap do |connection| 257 | device_orientation = UIDevice.currentDevice.orientation 258 | video_orientation = orientation_mapping.fetch(device_orientation, AVCaptureVideoOrientationPortrait) 259 | 260 | connection.setVideoOrientation(video_orientation) if connection.videoOrientationSupported? 261 | end 262 | end 263 | 264 | def orientation_mapping 265 | { UIDeviceOrientationPortrait => AVCaptureVideoOrientationPortrait, 266 | UIDeviceOrientationPortraitUpsideDown => AVCaptureVideoOrientationPortraitUpsideDown, 267 | UIDeviceOrientationLandscapeRight => AVCaptureVideoOrientationLandscapeLeft, 268 | UIDeviceOrientationLandscapeLeft => AVCaptureVideoOrientationLandscapeRight } 269 | end 270 | 271 | # iOS 4-8 272 | def assets_library 273 | @assets_library ||= ALAssetsLibrary.alloc.init 274 | end 275 | 276 | # iOS 8+ 277 | def photo_library 278 | @photo_library ||= PHPhotoLibrary.sharedPhotoLibrary 279 | end 280 | 281 | def configure_with_lock(&block) 282 | error_pointer = Pointer.new(:object) 283 | 284 | if device.lockForConfiguration(error_pointer) 285 | block.call 286 | 287 | device.unlockForConfiguration 288 | else 289 | error_callback.call(error_pointer[0]) 290 | end 291 | end 292 | 293 | def set_input(input) 294 | remove_inputs 295 | 296 | add_input(input) 297 | end 298 | 299 | def set_output(output) 300 | remove_outputs 301 | 302 | add_output(output) 303 | end 304 | 305 | def remove_inputs 306 | session.inputs.each do |input| 307 | remove_input(input) 308 | end 309 | end 310 | 311 | def remove_outputs 312 | session.outputs.each do |output| 313 | remove_output(output) 314 | end 315 | end 316 | 317 | def remove_input(input) 318 | session.removeInput(input) 319 | end 320 | 321 | def remove_output(output) 322 | session.removeOutput(output) 323 | end 324 | 325 | def add_input(input) 326 | session.addInput(input) if session.canAddInput(input) 327 | end 328 | 329 | def add_output(output) 330 | session.addOutput(output) if session.canAddOutput(output) 331 | end 332 | 333 | def default_camera 334 | AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) 335 | end 336 | 337 | def rear_camera 338 | capture_devices.select { |device| device.position == CAMERA_POSITIONS[:rear] }.first 339 | end 340 | 341 | def front_camera 342 | capture_devices.select { |device| device.position == CAMERA_POSITIONS[:front] }.first 343 | end 344 | 345 | def using_rear_camera? 346 | device ? device.position == CAMERA_POSITIONS[:rear] : false 347 | end 348 | 349 | def camera_devices 350 | { default: default_camera, rear: rear_camera, front: front_camera } 351 | end 352 | 353 | def capture_devices 354 | @capture_devices ||= AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) 355 | end 356 | 357 | def preview_layer_for_view(view, options = {}) 358 | AVCaptureVideoPreviewLayer.layerWithSession(session).tap do |layer| 359 | layer_bounds = view.layer.bounds 360 | 361 | layer.bounds = layer_bounds 362 | layer.position = CGPointMake(CGRectGetMidX(layer_bounds), CGRectGetMidY(layer_bounds)) 363 | layer.zPosition = options.fetch(:z_position, -100) 364 | layer.videoGravity = options.fetch(:video_gravity, AVLayerVideoGravityResizeAspectFill) 365 | end 366 | end 367 | 368 | def flash_on? 369 | device ? [FLASH_MODES[:on], FLASH_MODES[:auto]].include?(device.flashMode) : false 370 | end 371 | 372 | def session 373 | @session ||= AVCaptureSession.alloc.init 374 | end 375 | 376 | # iOS 4-9 377 | def still_image_output 378 | @still_image_output ||= AVCaptureStillImageOutput.alloc.init.tap do |output| 379 | settings = { 'AVVideoCodecKey' => AVVideoCodecJPEG } 380 | output.setOutputSettings(settings) 381 | end 382 | end 383 | 384 | # iOS 10+ 385 | def photo_output 386 | @photo_output ||= AVCapturePhotoOutput.alloc.init 387 | end 388 | end; end 389 | --------------------------------------------------------------------------------