Skip to content

Instantly share code, notes, and snippets.

@epologee
Created January 23, 2011 22:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save epologee/4d859f0c030342a682d4 to your computer and use it in GitHub Desktop.
Save epologee/4d859f0c030342a682d4 to your computer and use it in GitHub Desktop.
Put an end to all AS3 webcam access misery...
package com.epologee.puremvc.model.camera {
import com.epologee.development.logging.critical;
import com.epologee.development.logging.debug;
import com.epologee.development.logging.error;
import com.epologee.development.logging.info;
import com.epologee.development.logging.warn;
import com.epologee.time.Stopwatch;
import com.epologee.time.TimeDelay;
import org.puremvc.as3.multicore.patterns.proxy.Proxy;
import flash.display.Stage;
import flash.events.ActivityEvent;
import flash.events.Event;
import flash.events.StatusEvent;
import flash.events.TimerEvent;
import flash.media.Camera;
import flash.media.Video;
import flash.utils.getQualifiedClassName;
/**
* @author Eric-Paul Lecluse (c) epologee.com
*/
public class WebCamProxy extends Proxy {
public static const NAME : String = getQualifiedClassName(WebCamProxy2);
//
public static const SHOW_CAMERA_ACCESS_DIALOG : String = NAME + ":SHOW_CAMERA_ACCESS_DIALOG";
public static const HIDE_CAMERA_ACCESS_DIALOG : String = NAME + ":HIDE_CAMERA_ACCESS_DIALOG";
public static const CAMERA_ACCESS_GRANTED : String = NAME + ":CAMERA_ACCESS_GRANTED";
public static const CAMERA_ACCESS_DENIED : String = NAME + ":CAMERA_ACCESS_DENIED";
public static const CAMERA_SEARCH_STARTED : String = NAME + ":CAMERA_SEARCH_STARTED";
public static const CAMERA_SEARCH_PROGRESS : String = NAME + ":CAMERA_SEARCH_PROGRESS";
public static const CAMERA_SEARCH_FINISHED : String = NAME + ":CAMERA_SEARCH_FINISHED";
public static const CAMERA_FOUND : String = NAME + ":CAMERA_FOUND";
public static const CAMERA_NOT_FOUND : String = NAME + ":CAMERA_NOT_FOUND";
//
private static const CAMERA_STATUS_UNMUTED : String = "Camera.Unmuted";
private static const CAMERA_STATUS_MUTED : String = "Camera.Muted";
//
search static const TIMEOUT : int = 7000;
//
private var _stage : Stage;
private var _granted : Boolean;
private var _activeVideo : Video;
private var _activeCamera : Camera;
//
search var busy : Boolean;
search var timeout : TimeDelay;
search var camera : Camera;
search var video : Video;
search var stopWatch : Stopwatch;
search var progressBase : Number;
//
access var started : Boolean;
access var finished : Boolean;
access var camera : Camera;
access var video : Video;
public function WebCamProxy(inStage : Stage) {
super(NAME);
_stage = inStage;
}
/**
* @return whether the user has granted access to the camera.
*/
public function get granted() : Boolean {
return _granted;
}
/**
* @return the Video object that has the active Camera attached to it after the searchAndActivate() process completes.
*/
public function get activeVideo() : Video {
return search::video;
}
/**
* Activates the camera privacy check. This will end in one of these two notifications sent:
* #CAMERA_ACCESS_DENIED or #CAMERA_ACCESS_GRANTED
*
* In the process, these two notifications might get sent:
* #SHOW_CAMERA_ACCESS_DIALOG and #HIDE_CAMERA_ACCESS_DIALOG
*
* TODO: Figure out how to know if a user denied access and remembered the setting!!!
*/
public function accessCamera() : void {
use namespace access;
if (started) {
if (finished) {
if (camera.muted) {
access::sendNotification(CAMERA_ACCESS_DENIED, false);
} else {
access::sendNotification(CAMERA_ACCESS_GRANTED, true);
}
} else {
// let the process finish first
}
return;
}
started = true;
camera = Camera.getCamera();
if (!camera) {
access::sendNotification(CAMERA_ACCESS_DENIED, false);
return;
}
// If privacy settings are normal, attaching the camera will trigger the security dialog.
// The status event catches the result:
camera.addEventListener(StatusEvent.STATUS, handleStatusEvent);
if (!camera.muted) {
// If privacy settings are set to allow always, the muted property is already false
access::sendNotification(CAMERA_ACCESS_GRANTED, true);
return;
}
public::sendNotification(SHOW_CAMERA_ACCESS_DIALOG);
video = new Video();
video.attachCamera(camera);
}
/**
* Activates the search process that will eventually send one of two possible notifications:
* #CAMERA_FOUND or #CAMERA_NOT_FOUND
*
* Upon success, the activated camera is added as the notification body, and you can get the running camera via #activeCamera();
*/
public function searchAndActivateCamera() : void {
use namespace search;
if (busy) {
warn("already searching, not starting twice.");
return;
}
busy = true;
stopWatch = new Stopwatch();
if (_activeCamera) {
found(_activeCamera);
return;
}
sendNotification(CAMERA_SEARCH_STARTED);
tryCamera();
}
public function deactivateCamera() : void {
if (!(_activeCamera && _activeVideo)) {
critical("How can this be called if there is no active camera nor video? Check the flow.");
return;
}
_activeVideo.attachCamera(null);
}
access function handleStatusEvent(e : StatusEvent) : void {
use namespace access;
public::sendNotification(HIDE_CAMERA_ACCESS_DIALOG);
switch (e.code) {
case CAMERA_STATUS_MUTED:
access::sendNotification(CAMERA_ACCESS_DENIED, false);
break;
case CAMERA_STATUS_UNMUTED:
access::sendNotification(CAMERA_ACCESS_GRANTED, true);
break;
}
}
access function sendNotification(inNotification : String, inGranted : Boolean) : void {
use namespace access;
_granted = inGranted;
if (camera) {
camera.removeEventListener(StatusEvent.STATUS, handleStatusEvent);
}
if (video) {
video.attachCamera(null);
video.clear();
video = null;
}
finished = true;
info(inNotification);
super.sendNotification(inNotification);
}
search function tryCamera(inIndexList : Array = null) : void {
use namespace search;
if (!inIndexList) {
// The first time this method runs, there's no inIndex.
// that's when we initialize the Video object and try out the default camera.
search::video = new Video();
camera = Camera.getCamera();
} else {
camera.removeEventListener(ActivityEvent.ACTIVITY, handleActivity);
// get the camera at the current index
camera = Camera.getCamera(inIndexList.shift());
}
if (!camera) {
// we've run out of camera's to choose from.
giveUp();
return;
}
// We have a camera, we just don't know if it supplies an image
debug("Attempting " + (inIndexList ? "" : "default") + " camera: #" + camera.index + " --> " + camera.name);
// sync the framerate to the stage
camera.setMode(320, 240, _stage.frameRate);
// set the motion level and timeout so the camera fires only one activity event.
camera.setMotionLevel(1, 0);
camera.addEventListener(ActivityEvent.ACTIVITY, handleActivity);
// attach the camera (the first time this might trigger the access dialog).
search::video.attachCamera(camera);
if (!inIndexList) {
inIndexList = [];
// populate the list with all camera indexes, except for the one we just tried.
for (var i : int = 0;i < Camera.names.length;i++) {
if (i != camera.index) {
inIndexList.push(i);
}
}
}
// log the time for debugging
stopWatch.reset();
stopWatch.addEventListener(TimerEvent.TIMER, sendProgress);
sendProgress(null, inIndexList.length + 1);
stopWatch.start();
if (inIndexList.length) {
// start the inactivity time out to try out the next camera.
// if the timeout is not cancelled/reset within the given time, this method is called again
timeout = new TimeDelay(tryCamera, TIMEOUT, [inIndexList]);
} else {
timeout = new TimeDelay(giveUp, TIMEOUT);
}
}
search function sendProgress(e : Event = null, inCamerasLeft : Number = -1) : void {
use namespace search;
var chunk : Number = 1 / Camera.names.length;
if (inCamerasLeft >= 0) {
progressBase = Camera.names.length - inCamerasLeft;
}
var partial : Number = Math.min(stopWatch.time / TIMEOUT, 1);
var percentage : Number = chunk * (progressBase + partial);
sendNotification(CAMERA_SEARCH_PROGRESS, percentage);
}
search function giveUp() : void {
use namespace search;
busy = false;
error("No camera on this machine");
sendNotification(WebCamProxy2.CAMERA_SEARCH_FINISHED);
sendNotification(CAMERA_NOT_FOUND);
}
search function handleActivity(event : ActivityEvent) : void {
use namespace search;
if (timeout) {
timeout.reset();
}
var cam : Camera = (event.target) as Camera;
cam.removeEventListener(ActivityEvent.ACTIVITY, handleActivity);
found(cam);
}
search function found(inCamera : Camera) : void {
use namespace search;
busy = false;
_activeCamera = inCamera;
_activeVideo = video;
// if the camera has been deactivated, this will reactivate.
_activeVideo.attachCamera(_activeCamera);
sendNotification(CAMERA_SEARCH_FINISHED);
sendNotification(CAMERA_FOUND, _activeCamera);
info("Found camera at #" + _activeCamera.index + " --> " + _activeCamera.name + ", after " + stopWatch.stop() / 1000 + " seconds");
}
}
}
/**
* Don't expose variables in these namespaces
*/
namespace search;
namespace access;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment