class Motion::Capture

Constants

CAMERA_POSITIONS
FLASH_MODES

Attributes

device[R]
options[R]
preview_layer[R]

Public Class Methods

new(options = {}) click to toggle source
# File lib/project/motion-capture.rb, line 7
def initialize(options = {})
  @options = options
end

Public Instance Methods

attach(view, options = {}) click to toggle source
# File lib/project/motion-capture.rb, line 181
def attach(view, options = {})
  @preview_layer = preview_layer_for_view(view, options)

  view.layer.addSublayer(preview_layer)
end
authorize_camera(&block) click to toggle source
# File lib/project/motion-capture.rb, line 36
def authorize_camera(&block)
  AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: -> (success) {
    block.call(success)
  })
end
can_set_preset?(preset) click to toggle source
# File lib/project/motion-capture.rb, line 213
def can_set_preset?(preset)
  session.canSetSessionPreset(preset)
end
capture(&block) click to toggle source
# File lib/project/motion-capture.rb, line 76
def capture(&block)
  if defined?(AVCapturePhotoOutput) # iOS 10+
    Dispatch::Queue.new('motion-capture').async do
      ensure_running_session do
        update_video_orientation!
        @capture_callback = block
        capture_settings = AVCapturePhotoSettings.photoSettingsWithFormat(AVVideoCodecKey => AVVideoCodecJPEG)
        photo_output.capturePhotoWithSettings(capture_settings, delegate: self)
      end
    end
  else # iOS 4-9
    still_image_output.captureStillImageAsynchronouslyFromConnection(still_image_connection, completionHandler: -> (buffer, error) {
      if error
        error_callback.call(error)
      else
        image_data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
        block.call(image_data)
      end
    })
  end
end
captureOutput(output, didFinishProcessingPhoto: photo, error: error) click to toggle source

iOS 11+ AVCapturePhotoCaptureDelegate method

# File lib/project/motion-capture.rb, line 107
def captureOutput(output, didFinishProcessingPhoto: photo, error: error)
  if error
    error_callback.call(error)
  else
    @capture_callback.call(photo.fileDataRepresentation)
  end
end
capture_and_save(&block) click to toggle source
# File lib/project/motion-capture.rb, line 135
def capture_and_save(&block)
  capture do |jpeg_data|
    save_data(jpeg_data) do |asset_url|
      block.call(jpeg_data, asset_url)
    end
  end
end
capture_image(&block) click to toggle source
# File lib/project/motion-capture.rb, line 128
def capture_image(&block)
  capture do |jpeg_data|
    image = UIImage.imageWithData(jpeg_data)
    block.call(image)
  end
end
capture_image_and_save(&block) click to toggle source
# File lib/project/motion-capture.rb, line 143
def capture_image_and_save(&block)
  capture do |jpeg_data|
    save_data(jpeg_data) do |asset_url|
      image = UIImage.imageWithData(jpeg_data)
      block.call(image, asset_url)
    end
  end
end
configure_session() click to toggle source
# File lib/project/motion-capture.rb, line 42
def configure_session
  session.beginConfiguration

  set_preset(options.fetch(:preset, AVCaptureSessionPresetPhoto))

  use_camera(options.fetch(:device, :default))

  if defined?(AVCapturePhotoOutput) # iOS 10+
    add_output(photo_output)
  else # iOS 4-9
    add_output(still_image_output)
  end

  session.commitConfiguration
end
ensure_running_session(&block) click to toggle source
# File lib/project/motion-capture.rb, line 98
def ensure_running_session(&block)
  start! unless @starting || session.running?
  while @starting || !session.running?
    # wait for session to start...
  end
  block.call
end
flash() click to toggle source
# File lib/project/motion-capture.rb, line 225
def flash
  device.flashMode if @device
end
flash_mode_available?(mode) click to toggle source
# File lib/project/motion-capture.rb, line 234
def flash_mode_available?(mode)
  FLASH_MODES.keys.include?(mode) && device.isFlashModeSupported(FLASH_MODES[mode])
end
on_error(&block) click to toggle source
# File lib/project/motion-capture.rb, line 11
def on_error(&block)
  @error_callback = block
end
preset() click to toggle source
# File lib/project/motion-capture.rb, line 221
def preset
  session.sessionPreset if @session
end
running?() click to toggle source
# File lib/project/motion-capture.rb, line 58
def running?
  @session && session.running?
end
save_data(jpeg_data, &block) click to toggle source
# File lib/project/motion-capture.rb, line 152
def save_data(jpeg_data, &block)
  if defined?(PHPhotoLibrary) # iOS 8+
    save_to_photo_library(jpeg_data, &block)
  else # iOS 4-8
    save_to_assets_library(jpeg_data, &block)
  end
end
save_to_assets_library(jpeg_data, &block) click to toggle source

iOS 4-8

# File lib/project/motion-capture.rb, line 161
def save_to_assets_library(jpeg_data, &block)
  assets_library.writeImageDataToSavedPhotosAlbum(jpeg_data, metadata: nil, completionBlock: -> (asset_url, error) {
    error ? error_callback.call(error) : block.call(asset_url)
  })
end
save_to_photo_library(jpeg_data, &block) click to toggle source

iOS 8+

# File lib/project/motion-capture.rb, line 168
def save_to_photo_library(jpeg_data, &block)
  photo_library.performChanges(-> {
    image = UIImage.imageWithData(jpeg_data)
    PHAssetChangeRequest.creationRequestForAssetFromImage(image)
  }, completionHandler: -> (success, error) {
    if error
      error_callback.call(error)
    else
      block.call(nil) # asset url is not returned in completion block
    end
  })
end
set_flash(mode = :auto) click to toggle source

iOS 4-9

# File lib/project/motion-capture.rb, line 230
def set_flash(mode = :auto)
  configure_with_lock { device.flashMode = FLASH_MODES[mode] } if flash_mode_available?(mode)
end
set_preset(preset) click to toggle source
# File lib/project/motion-capture.rb, line 217
def set_preset(preset)
  session.sessionPreset = preset if can_set_preset? preset
end
start!() click to toggle source
# File lib/project/motion-capture.rb, line 15
def start!
  return if session.running?
  if defined?(AVCapturePhotoOutput) # iOS 10+
    @starting = true
    authorize_camera do |success|
      if success
        Dispatch::Queue.new('motion-capture').async do
          configure_session
          session.startRunning
          @starting = false
        end
      else
        @starting = false
      end
    end
  else # iOS 4-9
    configure_session
    session.startRunning
  end
end
stop!() click to toggle source
# File lib/project/motion-capture.rb, line 62
def stop!
  session.stopRunning

  remove_outputs
  remove_inputs

  preview_layer.removeFromSuperlayer if preview_layer && preview_layer.superlayer

  @still_image_output = nil
  @photo_output       = nil
  @session            = nil
  @preview_layer      = nil
end
toggle_camera() click to toggle source
# File lib/project/motion-capture.rb, line 199
def toggle_camera
  target_camera = using_rear_camera? ? :front : :rear

  use_camera(target_camera)
end
toggle_flash() click to toggle source
# File lib/project/motion-capture.rb, line 205
def toggle_flash
  if device && device.hasFlash
    target_mode = flash_on? ? :off : :on

    set_flash(target_mode)
  end
end
use_camera(target_camera = :default) click to toggle source
# File lib/project/motion-capture.rb, line 187
def use_camera(target_camera = :default)
  @device = camera_devices[target_camera]

  error_pointer = Pointer.new(:object)

  if input = AVCaptureDeviceInput.deviceInputWithDevice(device, error: error_pointer)
    set_input(input)
  else
    error_callback.call(error_pointer[0])
  end
end

Private Instance Methods

add_input(input) click to toggle source
# File lib/project/motion-capture.rb, line 325
def add_input(input)
  session.addInput(input) if session.canAddInput(input)
end
add_output(output) click to toggle source
# File lib/project/motion-capture.rb, line 329
def add_output(output)
  session.addOutput(output) if session.canAddOutput(output)
end
assets_library() click to toggle source

iOS 4-8

# File lib/project/motion-capture.rb, line 272
def assets_library
  @assets_library ||= ALAssetsLibrary.alloc.init
end
camera_devices() click to toggle source
# File lib/project/motion-capture.rb, line 349
def camera_devices
  { default: default_camera, rear: rear_camera, front: front_camera }
end
capture_devices() click to toggle source
# File lib/project/motion-capture.rb, line 353
def capture_devices
  @capture_devices ||= AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
end
configure_with_lock(&block) click to toggle source
# File lib/project/motion-capture.rb, line 281
def configure_with_lock(&block)
  error_pointer = Pointer.new(:object)

  if device.lockForConfiguration(error_pointer)
    block.call

    device.unlockForConfiguration
  else
    error_callback.call(error_pointer[0])
  end
end
default_camera() click to toggle source
# File lib/project/motion-capture.rb, line 333
def default_camera
  AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
end
error_callback() click to toggle source
# File lib/project/motion-capture.rb, line 240
def error_callback
  @error_callback ||= -> (error) { p "An error occurred: #{error.localizedDescription}." }
end
flash_on?() click to toggle source
# File lib/project/motion-capture.rb, line 368
def flash_on?
  device ? [FLASH_MODES[:on], FLASH_MODES[:auto]].include?(device.flashMode) : false
end
front_camera() click to toggle source
# File lib/project/motion-capture.rb, line 341
def front_camera
  capture_devices.select { |device| device.position == CAMERA_POSITIONS[:front] }.first
end
orientation_mapping() click to toggle source
# File lib/project/motion-capture.rb, line 264
def orientation_mapping
  { UIDeviceOrientationPortrait           => AVCaptureVideoOrientationPortrait,
    UIDeviceOrientationPortraitUpsideDown => AVCaptureVideoOrientationPortraitUpsideDown,
    UIDeviceOrientationLandscapeRight     => AVCaptureVideoOrientationLandscapeLeft,
    UIDeviceOrientationLandscapeLeft      => AVCaptureVideoOrientationLandscapeRight }
end
photo_library() click to toggle source

iOS 8+

# File lib/project/motion-capture.rb, line 277
def photo_library
  @photo_library ||= PHPhotoLibrary.sharedPhotoLibrary
end
photo_output() click to toggle source

iOS 10+

# File lib/project/motion-capture.rb, line 385
def photo_output
  @photo_output ||= AVCapturePhotoOutput.alloc.init
end
preview_layer_for_view(view, options = {}) click to toggle source
# File lib/project/motion-capture.rb, line 357
def preview_layer_for_view(view, options = {})
  AVCaptureVideoPreviewLayer.layerWithSession(session).tap do |layer|
    layer_bounds = view.layer.bounds

    layer.bounds       = layer_bounds
    layer.position     = CGPointMake(CGRectGetMidX(layer_bounds), CGRectGetMidY(layer_bounds))
    layer.zPosition    = options.fetch(:z_position, -100)
    layer.videoGravity = options.fetch(:video_gravity, AVLayerVideoGravityResizeAspectFill)
  end
end
rear_camera() click to toggle source
# File lib/project/motion-capture.rb, line 337
def rear_camera
  capture_devices.select { |device| device.position == CAMERA_POSITIONS[:rear] }.first
end
remove_input(input) click to toggle source
# File lib/project/motion-capture.rb, line 317
def remove_input(input)
  session.removeInput(input)
end
remove_inputs() click to toggle source
# File lib/project/motion-capture.rb, line 305
def remove_inputs
  session.inputs.each do |input|
    remove_input(input)
  end
end
remove_output(output) click to toggle source
# File lib/project/motion-capture.rb, line 321
def remove_output(output)
  session.removeOutput(output)
end
remove_outputs() click to toggle source
# File lib/project/motion-capture.rb, line 311
def remove_outputs
  session.outputs.each do |output|
    remove_output(output)
  end
end
session() click to toggle source
# File lib/project/motion-capture.rb, line 372
def session
  @session ||= AVCaptureSession.alloc.init
end
set_input(input) click to toggle source
# File lib/project/motion-capture.rb, line 293
def set_input(input)
  remove_inputs

  add_input(input)
end
set_output(output) click to toggle source
# File lib/project/motion-capture.rb, line 299
def set_output(output)
  remove_outputs

  add_output(output)
end
still_image_connection() click to toggle source

iOS 4-9

# File lib/project/motion-capture.rb, line 245
def still_image_connection
  still_image_output.connectionWithMediaType(AVMediaTypeVideo).tap do |connection|
    device_orientation = UIDevice.currentDevice.orientation
    video_orientation  = orientation_mapping.fetch(device_orientation, AVCaptureVideoOrientationPortrait)

    connection.setVideoOrientation(video_orientation) if connection.videoOrientationSupported?
  end
end
still_image_output() click to toggle source

iOS 4-9

# File lib/project/motion-capture.rb, line 377
def still_image_output
  @still_image_output ||= AVCaptureStillImageOutput.alloc.init.tap do |output|
    settings = { 'AVVideoCodecKey' => AVVideoCodecJPEG }
    output.setOutputSettings(settings)
  end
end
update_video_orientation!() click to toggle source

iOS 10+

# File lib/project/motion-capture.rb, line 255
def update_video_orientation!
  photo_output.connectionWithMediaType(AVMediaTypeVideo).tap do |connection|
    device_orientation = UIDevice.currentDevice.orientation
    video_orientation  = orientation_mapping.fetch(device_orientation, AVCaptureVideoOrientationPortrait)

    connection.setVideoOrientation(video_orientation) if connection.videoOrientationSupported?
  end
end
using_rear_camera?() click to toggle source
# File lib/project/motion-capture.rb, line 345
def using_rear_camera?
  device ? device.position == CAMERA_POSITIONS[:rear] : false
end