Skip to content

Commit

Permalink
Merge pull request Khan#43 from Khan/camera-layers
Browse files Browse the repository at this point in the history
Camera layers
  • Loading branch information
jbrennan committed Mar 5, 2015
2 parents f445ef7 + b3abf2e commit 968dc7c
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 1 deletion.
23 changes: 23 additions & 0 deletions Examples/CameraLayer/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var cameraLayer = new CameraLayer()
cameraLayer.width = Layer.root.width * 0.5
cameraLayer.height = Layer.root.height * 0.5
cameraLayer.cameraPosition = CameraPosition.Front
cameraLayer.x = Layer.root.x
cameraLayer.y = Layer.root.y

var flipButton = new TextLayer()
flipButton.fontName = "Futura"
flipButton.fontSize = 30
flipButton.text = "Flip"
flipButton.x = Layer.root.x
flipButton.y = 100
flipButton.animators.alpha.springBounciness = 0
flipButton.animators.alpha.springSpeed = 20

Layer.root.touchBeganHandler = function() {
flipButton.animators.alpha.target = 0.5
}
Layer.root.touchEndedHandler = function(touchSequence) {
cameraLayer.cameraPosition = (cameraLayer.cameraPosition === CameraPosition.Front) ? CameraPosition.Back : CameraPosition.Front
flipButton.animators.alpha.target = 1.0
}
4 changes: 4 additions & 0 deletions Protoscope/SessionInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class SessionInteractor {
}
}
return nil
},
exceptionHandler: { [weak self] exception in
self?.exceptionHandler(exception)
return
}
)

Expand Down
8 changes: 8 additions & 0 deletions Prototope.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@
EBC43D841A81BA360075272A /* HeartbeatBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC43D831A81BA360075272A /* HeartbeatBridge.swift */; };
EBC43D861A81BBC30075272A /* HeartbeatBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC43D851A81BBC30075272A /* HeartbeatBridgeTests.swift */; };
EBC43D881A81C47A0075272A /* SoundBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC43D871A81C47A0075272A /* SoundBridge.swift */; };
EBCEBBD01AA8DB7F00B58A6C /* CameraLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCEBBCF1AA8DB7F00B58A6C /* CameraLayer.swift */; };
EBCEBBDF1AA8DC9800B58A6C /* CameraLayerBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCEBBDE1AA8DC9800B58A6C /* CameraLayerBridge.swift */; };
EBD43A9F1A9186F900BB2E6A /* TextLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD43A9E1A9186F900BB2E6A /* TextLayer.swift */; };
EBD43AAF1A918A8900BB2E6A /* TextLayerBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD43AAD1A918A8200BB2E6A /* TextLayerBridge.swift */; };
EBE17A0619E65D1B00C308E5 /* Timing.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE17A0519E65D1B00C308E5 /* Timing.swift */; };
Expand Down Expand Up @@ -527,6 +529,8 @@
EBC43D831A81BA360075272A /* HeartbeatBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartbeatBridge.swift; sourceTree = "<group>"; };
EBC43D851A81BBC30075272A /* HeartbeatBridgeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartbeatBridgeTests.swift; sourceTree = "<group>"; };
EBC43D871A81C47A0075272A /* SoundBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoundBridge.swift; sourceTree = "<group>"; };
EBCEBBCF1AA8DB7F00B58A6C /* CameraLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraLayer.swift; sourceTree = "<group>"; };
EBCEBBDE1AA8DC9800B58A6C /* CameraLayerBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraLayerBridge.swift; sourceTree = "<group>"; };
EBD43A9E1A9186F900BB2E6A /* TextLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextLayer.swift; sourceTree = "<group>"; };
EBD43AAD1A918A8200BB2E6A /* TextLayerBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextLayerBridge.swift; sourceTree = "<group>"; };
EBE17A0519E65D1B00C308E5 /* Timing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timing.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -787,6 +791,7 @@
7520648F1A89E39F005E38A2 /* Behaviors.swift */,
9619D3AB1A8E56F20022DC54 /* Layer+Layout.swift */,
EBD43A9E1A9186F900BB2E6A /* TextLayer.swift */,
EBCEBBCF1AA8DB7F00B58A6C /* CameraLayer.swift */,
961AC2051A93B9D500ED9744 /* Speech.swift */,
9EC148211A20FB1C004C7129 /* ThirdParty */,
EBB3505D1A166E5E0046AD07 /* Utilities */,
Expand Down Expand Up @@ -1013,6 +1018,7 @@
EBB43C941A8BEB2200CE943C /* VideoLayerBridge.swift */,
9632F6621A8D4CD3003D386B /* ScrollLayerBridge.swift */,
EBD43AAD1A918A8200BB2E6A /* TextLayerBridge.swift */,
EBCEBBDE1AA8DC9800B58A6C /* CameraLayerBridge.swift */,
961AC2141A93D45D00ED9744 /* SpeechBridge.swift */,
);
name = "Bridged Types";
Expand Down Expand Up @@ -1529,6 +1535,7 @@
EBD43AAF1A918A8900BB2E6A /* TextLayerBridge.swift in Sources */,
EBC43D641A8010770075272A /* ImageBridge.swift in Sources */,
EBC43D6F1A81916E0075272A /* Context.swift in Sources */,
EBCEBBDF1AA8DC9800B58A6C /* CameraLayerBridge.swift in Sources */,
EBC43D671A8042740075272A /* GeometryBridge.swift in Sources */,
9632F6641A8D4D08003D386B /* ScrollLayerBridge.swift in Sources */,
EBC43D841A81BA360075272A /* HeartbeatBridge.swift in Sources */,
Expand Down Expand Up @@ -1628,6 +1635,7 @@
files = (
EBE3A77519DF79E900A77736 /* Layer.swift in Sources */,
EBD43A9F1A9186F900BB2E6A /* TextLayer.swift in Sources */,
EBCEBBD01AA8DB7F00B58A6C /* CameraLayer.swift in Sources */,
CD11D82B1A815AD3004F1197 /* Particle.swift in Sources */,
961AC2061A93B9D500ED9744 /* Speech.swift in Sources */,
CD3F93A01A852AF500B964FC /* ParticlePreset.swift in Sources */,
Expand Down
86 changes: 86 additions & 0 deletions Prototope/CameraLayer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// CameraLayer.swift
// Prototope
//
// Created by Andy Matuschak on 3/5/15.
// Copyright (c) 2015 Khan Academy. All rights reserved.
//

import Foundation
import AVFoundation

/** A layer that shows the output of one of the device's cameras. Defaults to using the back camera. */
public class CameraLayer: Layer {
public enum CameraPosition: Printable {
/** The device's front-facing camera. */
case Front

/** The device's back-facing camera. */
case Back

private var avCaptureDevicePosition: AVCaptureDevicePosition {
switch self {
case .Front: return .Front
case .Back: return .Back
}
}

public var description: String {
switch self {
case .Front: return "Front"
case .Back: return "Back"
}
}
}

/** Selects which camera to use. */
public var cameraPosition: CameraPosition {
didSet { updateSession() }
}

private var captureSession: AVCaptureSession?

public init(parent: Layer? = Layer.root, name: String? = nil) {
self.cameraPosition = .Back
super.init(parent: parent, name: name, viewClass: CameraView.self)
updateSession()
}

deinit {
captureSession?.stopRunning()
}

private func updateSession() {
// Find device matching camera setting
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) as! [AVCaptureDevice]
if let device = filter(devices, { device in return device.position == self.cameraPosition.avCaptureDevicePosition }).first {
var error: NSError?
if let input = AVCaptureDeviceInput(device: device, error: &error) {
captureSession?.stopRunning()

captureSession = AVCaptureSession()
captureSession!.addInput(input)
captureSession!.startRunning()
cameraLayer.session = captureSession!
cameraLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
} else {
Environment.currentEnvironment!.exceptionHandler("Couldn't create camera device: \(error)")
}

} else {
Environment.currentEnvironment!.exceptionHandler("Could not find a \(cameraPosition.description.lowercaseString) camera on this device")
}

}

private var cameraLayer: AVCaptureVideoPreviewLayer {
return (self.view as! CameraView).layer as! AVCaptureVideoPreviewLayer
}

/** Underlying camera view class. */
private class CameraView: UIView {
override class func layerClass() -> AnyClass {
return AVCaptureVideoPreviewLayer.self
}
}
}
7 changes: 6 additions & 1 deletion Prototope/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ public struct Environment {
public let rootLayer: Layer
public let imageProvider: String -> UIImage?
public let soundProvider: String -> NSData?
public let exceptionHandler: String -> Void
let behaviorDriver: BehaviorDriver

public static var currentEnvironment: Environment?

public init(rootView: UIView, imageProvider: String -> UIImage?, soundProvider: String -> NSData?) {
public init(rootView: UIView, imageProvider: String -> UIImage?, soundProvider: String -> NSData?, exceptionHandler: String -> Void) {
self.rootLayer = Layer(hostingView: rootView, name: "Root")

// TODO: move defaultSpec into Environment.
Expand All @@ -28,6 +29,7 @@ public struct Environment {

self.imageProvider = imageProvider
self.soundProvider = soundProvider
self.exceptionHandler = exceptionHandler
self.behaviorDriver = BehaviorDriver()
}

Expand All @@ -48,6 +50,9 @@ public struct Environment {
}
}
return nil
},
exceptionHandler: { exception in
fatalError("Prototope exception: \(exception)")
}
)
}
Expand Down
71 changes: 71 additions & 0 deletions PrototopeJSBridge/CameraLayerBridge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// CameraLayerBridge.swift
// Prototope
//
// Created by Andy Matuschak on 2/15/15.
// Copyright (c) 2015 Khan Academy. All rights reserved.
//

import Foundation
import Prototope
import JavaScriptCore

@objc public protocol CameraLayerJSExport: JSExport {
var cameraPosition: JSValue { get set }
}

@objc public class CameraLayerBridge: LayerBridge, CameraLayerJSExport, BridgeType {
var cameraLayer: CameraLayer { return layer as! CameraLayer }

public override class func addToContext(context: JSContext) {
context.setObject(self, forKeyedSubscript: "CameraLayer")
}

public required init?(args: NSDictionary) {
let parentLayer = (args["parent"] as! LayerBridge?)?.layer
let cameraLayer = CameraLayer(parent: parentLayer, name: (args["name"] as! String?))
super.init(cameraLayer)
}

public var cameraPosition: JSValue {
get { return CameraPositionBridge.encodeCameraPosition(cameraLayer.cameraPosition, inContext: JSContext.currentContext()) }
set { cameraLayer.cameraPosition = CameraPositionBridge.decodeCameraPosition(newValue) }
}

}

public class CameraPositionBridge: NSObject, BridgeType {
enum RawCameraPosition: Int {
case Front = 0
case Back = 1
}

public class func addToContext(context: JSContext) {
let alignmentObject = JSValue(newObjectInContext: context)
alignmentObject.setObject(RawCameraPosition.Front.rawValue, forKeyedSubscript: "Front")
alignmentObject.setObject(RawCameraPosition.Back.rawValue, forKeyedSubscript: "Back")
context.setObject(alignmentObject, forKeyedSubscript: "CameraPosition")
}

public class func encodeCameraPosition(cameraPosition: Prototope.CameraLayer.CameraPosition, inContext context: JSContext) -> JSValue {
var rawCameraPosition: RawCameraPosition
switch cameraPosition {
case .Front: rawCameraPosition = .Front
case .Back: rawCameraPosition = .Back
}
return JSValue(int32: Int32(rawCameraPosition.rawValue), inContext: context)
}

public class func decodeCameraPosition(bridgedCameraPosition: JSValue) -> Prototope.CameraLayer.CameraPosition! {
if let rawCameraPosition = RawCameraPosition(rawValue: Int(bridgedCameraPosition.toInt32())) {
switch rawCameraPosition {
case .Front: return .Front
case .Back: return .Back
}
} else {
Environment.currentEnvironment!.exceptionHandler("Unknown camera position: \(bridgedCameraPosition)")
return nil
}
}
}

2 changes: 2 additions & 0 deletions PrototopeJSBridge/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,7 @@ public class Context {
TextLayerBridge.addToContext(context)
SpeechBridge.addToContext(context)
TextAlignmentBridge.addToContext(context)
CameraLayerBridge.addToContext(context)
CameraPositionBridge.addToContext(context)
}
}

0 comments on commit 968dc7c

Please sign in to comment.