diff --git a/Gruntfile.js b/Gruntfile.js index 86977dd..1b3d72a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -32,7 +32,7 @@ module.exports = function(grunt) { dest: 'dist/angular-camera.js' } }, - ngmin: { + ngAnnotate: { src: { src: '<%= concat.src.src %>', dest: '<%= concat.src.dest %>' @@ -47,7 +47,7 @@ module.exports = function(grunt) { } }); - grunt.registerTask('build', ['dev', 'clean:dist', 'concat', 'ngmin', 'uglify']); + grunt.registerTask('build', ['dev', 'clean:dist', 'concat', 'ngAnnotate', 'uglify']); grunt.registerTask('dev', ['coffee:dev']); grunt.registerTask('default', ['build']); }; diff --git a/bower.json b/bower.json index 9a5c4ad..879f964 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-camera", - "version": "0.1.3", + "version": "0.2.2", "homepage": "https://github.com/onemightyroar/angular-camera", "authors": [ "Zach Dunn " @@ -14,7 +14,7 @@ "video" ], "dependencies": { - "angular": "~1.2.0" + "angular": "~1.3" }, "license": "GNU" } diff --git a/dist/angular-camera.js b/dist/angular-camera.js index 2284cc9..c1b902a 100644 --- a/dist/angular-camera.js +++ b/dist/angular-camera.js @@ -1,163 +1,221 @@ -(function () { +(function() { 'use strict'; - angular.module('omr.directives', []).directive('ngCamera', [ - '$timeout', - '$sce', - function ($timeout, $sce) { - return { - require: 'ngModel', - template: '

Loading Camera...

Couldn\'t find a camera to use

{{countdownText}}

', - replace: false, - transclude: true, - restrict: 'E', - scope: { - type: '@', - media: '=ngModel', - width: '@', - height: '@', - overlaySrc: '=', - countdown: '@', - captureCallback: '&capture', - enabled: '=', - captureMessage: '@' - }, - link: function (scope, element, attrs, ngModel) { - scope.activeCountdown = false; - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; - scope.$on('$destroy', function () { - if (scope.stream && typeof scope.stream.stop === 'function') { - scope.stream.stop(); - } - }); - scope.enableCamera = function () { - return navigator.getUserMedia({ - audio: false, - video: true - }, function (stream) { - return scope.$apply(function () { - scope.stream = stream; - scope.isLoaded = true; - return scope.videoStream = $sce.trustAsResourceUrl(window.URL.createObjectURL(stream)); - }); - }, function (error) { - return scope.$apply(function () { - scope.isLoaded = true; - return scope.noCamera = true; - }); + angular.module('omr.directives', []).directive('ngCamera', ["$timeout", "$sce", function($timeout, $sce) { + return { + require: 'ngModel', + template: '
\ +

Loading Camera...

\ +

Couldn\'t find a camera to use

\ +
\ +
\ +

{{countdownText}}

\ +
\ + \ + \ + \ +
\ +
\ + \ +
\ +
', + replace: false, + transclude: true, + restrict: 'E', + scope: { + type: '@', + media: '=ngModel', + width: '@', + height: '@', + captureWidth: '@', + captureHeight: '@', + overlaySrc: '=', + countdown: '=', + captureCallback: '&capture', + enabled: '=', + captureMessage: "@" + }, + link: function(scope, element, attrs, ngModel) { + if (!scope.captureWidth) { + scope.captureWidth = scope.width; + } + if (!scope.captureHeight) { + scope.captureHeight = scope.height; + } + scope.activeCountdown = false; + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; + window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; + scope.$on('$destroy', function() { + if (scope.stream && typeof scope.stream.stop === 'function') { + scope.stream.stop(); + } + }); + /** + * @description Set mediastream source and notify camera + */ + + scope.enableCamera = function() { + return navigator.getUserMedia({ + audio: false, + video: true + }, function(stream) { + return scope.$apply(function() { + scope.stream = stream; + scope.isLoaded = true; + return scope.videoStream = $sce.trustAsResourceUrl(window.URL.createObjectURL(stream)); }); - }; - scope.disableCamera = function () { - return navigator.getUserMedia({ - audio: false, - video: true - }, function (stream) { - return scope.$apply(function () { - return scope.videoStream = ''; - }); + }, function(error) { + return scope.$apply(function() { + scope.isLoaded = true; + return scope.noCamera = true; }); - }; - scope.takePicture = function () { - var canvas, context, countdownTick, countdownTime; - canvas = window.document.getElementById('ng-photo-canvas'); - countdownTime = scope.countdown != null ? parseInt(scope.countdown) * 1000 : 0; - if (canvas != null) { - if (countdownTime > 0) { - scope.activeCountdown = true; - scope.hideUI = true; - } - context = canvas.getContext('2d'); - if (scope.countdownTimer) { - $timeout.cancel(scope.countdownTimer); - } - scope.countdownTimer = $timeout(function () { - var cameraFeed; - scope.activeCountdown = false; - cameraFeed = window.document.getElementById('ng-camera-feed'); - context.drawImage(cameraFeed, 0, 0, scope.width, scope.height); - if (scope.overlaySrc != null) { - scope.addFrame(context, scope.overlaySrc, function (image) { - scope.$apply(function () { - return scope.media = canvas.toDataURL('image/jpeg'); - }); - if (scope.captureCallback != null) { - return scope.captureCallback(scope.media); - } + }); + }; + /** + * @description Disable mediastream source and notify camera + */ + + scope.disableCamera = function() { + return navigator.getUserMedia({ + audio: false, + video: true + }, function(stream) { + return scope.$apply(function() { + return scope.videoStream = ""; + }); + }); + }; + scope.$on('CAMERA_PICTURE_REQUIRED', function() { + return scope.takePicture(); + }); + /** + * @description Capture current state of video stream as photo + */ + + scope.takePicture = function() { + var canvas, context, countdownTick, countdownTime; + canvas = window.document.getElementById('ng-photo-canvas'); + countdownTime = scope.countdown != null ? scope.countdown * 1000 : 0; + if (canvas != null) { + if (countdownTime > 0) { + scope.activeCountdown = true; + scope.hideUI = true; + } + context = canvas.getContext('2d'); + if (scope.countdownTimer) { + $timeout.cancel(scope.countdownTimer); + } + scope.countdownTimer = $timeout(function() { + var cameraFeed; + scope.activeCountdown = false; + cameraFeed = window.document.getElementById('ng-camera-feed'); + context.drawImage(cameraFeed, 0, 0, scope.captureWidth, scope.captureHeight); + if (scope.overlaySrc != null) { + scope.addFrame(context, scope.overlaySrc, function(image) { + scope.$apply(function() { + return scope.media = canvas.toDataURL('image/jpeg'); }); - } else { - scope.media = canvas.toDataURL('image/jpeg'); if (scope.captureCallback != null) { - scope.captureCallback(scope.media); - } - } - return scope.hideUI = false; - }, countdownTime + 1000); - scope.countdownText = parseInt(scope.countdown); - countdownTick = setInterval(function () { - return scope.$apply(function () { - var nextTick; - nextTick = parseInt(scope.countdownText) - 1; - if (nextTick === 0) { - scope.countdownText = scope.captureMessage != null ? scope.captureMessage : 'GO!'; - return clearInterval(countdownTick); - } else { - return scope.countdownText = nextTick; + return scope.captureCallback(scope.media); } }); - }, 1000); - } else { + } else { + scope.media = canvas.toDataURL('image/jpeg'); + if (scope.captureCallback != null) { + scope.captureCallback(scope.media); + } + } + return scope.hideUI = false; + }, countdownTime + 1000); + scope.countdownText = scope.countdown; + countdownTick = setInterval(function() { + return scope.$apply(function() { + var nextTick; + nextTick = scope.countdownText - 1; + if (nextTick === 0) { + scope.countdownText = scope.captureMessage != null ? scope.captureMessage : 'GO!'; + return clearInterval(countdownTick); + } else { + return scope.countdownText = nextTick; + } + }); + }, 1000); + } else { + + } + return false; + }; + /** + * @description Add overlay frame to canvas render + * @param {Object} context Reference to target canvas context + */ + + scope.addFrame = function(context, url, callback) { + var overlay; + if (callback == null) { + callback = false; + } + overlay = new Image(); + overlay.onload = function() { + context.drawImage(overlay, 0, 0, scope.width, scope.height); + if (callback) { + return callback(context); } - return false; }; - scope.addFrame = function (context, url, callback) { - var overlay; - if (callback == null) { - callback = false; - } - overlay = new Image(); - overlay.onload = function () { - context.drawImage(overlay, 0, 0, scope.width, scope.height); - if (callback) { - return callback(context); - } + overlay.crossOrigin = ''; + return overlay.src = url; + }; + /** + * @description Keeps a packaged version of media ready + * @param {Base64} newVal Prefix-stripped Base64 of of canvas image + */ + + scope.$watch('media', function(newVal) { + if (newVal != null) { + return scope.packagedMedia = scope.media.replace(/^data:image\/\w+;base64,/, ""); + } + }); + /** + * @description Preloader for overlay image + */ + + scope.$watch('overlaySrc', function(newVal, oldVal) { + var preloader; + if (scope.overlaySrc != null) { + scope.isLoaded = false; + preloader = new Image(); + preloader.crossOrigin = ''; + preloader.src = newVal; + return preloader.onload = function() { + return scope.$apply(function() { + return scope.isLoaded = true; + }); }; - overlay.crossOrigin = ''; - return overlay.src = url; - }; - scope.$watch('media', function (newVal) { - if (newVal != null) { - return scope.packagedMedia = scope.media.replace(/^data:image\/\w+;base64,/, ''); + } else { + return scope.isLoaded = true; + } + }); + /** + * @description Watch for when to turn on/off camera feed + */ + + scope.$watch('enabled', function(newVal, oldVal) { + if (newVal) { + if (!oldVal) { + return scope.enableCamera(); } - }); - scope.$watch('overlaySrc', function (newVal, oldVal) { - var preloader; - if (scope.overlaySrc != null) { - scope.isLoaded = false; - preloader = new Image(); - preloader.crossOrigin = ''; - preloader.src = newVal; - return preloader.onload = function () { - return scope.$apply(function () { - return scope.isLoaded = true; - }); - }; - } else { - return scope.isLoaded = true; + } else { + if (oldVal != null) { + return scope.disableCamera(); } - }); - scope.$watch('enabled', function (newVal, oldVal) { - if (newVal) { - if (!oldVal) { - return scope.enableCamera(); - } - } else { - if (oldVal != null) { - return scope.disableCamera(); - } - } - }); - return scope.$watch('type', function () { - switch (scope.type) { + } + }); + /** + * @description Check format type of camera. + * @todo Future support for different media types (GIF, Video) + */ + + return scope.$watch('type', function() { + switch (scope.type) { case 'photo': if (scope.enabled) { return scope.enableCamera(); @@ -167,10 +225,10 @@ if (scope.enabled) { return scope.enableCamera(); } - } - }); - } - }; - } - ]); -}.call(this)); \ No newline at end of file + } + }); + } + }; + }]); + +}).call(this); diff --git a/dist/angular-camera.min.js b/dist/angular-camera.min.js index 7257f81..f7e6657 100644 --- a/dist/angular-camera.min.js +++ b/dist/angular-camera.min.js @@ -1 +1 @@ -(function(){"use strict";angular.module("omr.directives",[]).directive("ngCamera",["$timeout","$sce",function(a,b){return{require:"ngModel",template:'

Loading Camera...

Couldn\'t find a camera to use

{{countdownText}}

',replace:!1,transclude:!0,restrict:"E",scope:{type:"@",media:"=ngModel",width:"@",height:"@",overlaySrc:"=",countdown:"@",captureCallback:"&capture",enabled:"=",captureMessage:"@"},link:function(c){return c.activeCountdown=!1,navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,window.URL=window.URL||window.webkitURL||window.mozURL||window.msURL,c.$on("$destroy",function(){c.stream&&"function"==typeof c.stream.stop&&c.stream.stop()}),c.enableCamera=function(){return navigator.getUserMedia({audio:!1,video:!0},function(a){return c.$apply(function(){return c.stream=a,c.isLoaded=!0,c.videoStream=b.trustAsResourceUrl(window.URL.createObjectURL(a))})},function(){return c.$apply(function(){return c.isLoaded=!0,c.noCamera=!0})})},c.disableCamera=function(){return navigator.getUserMedia({audio:!1,video:!0},function(){return c.$apply(function(){return c.videoStream=""})})},c.takePicture=function(){var b,d,e,f;return b=window.document.getElementById("ng-photo-canvas"),f=null!=c.countdown?1e3*parseInt(c.countdown):0,null!=b&&(f>0&&(c.activeCountdown=!0,c.hideUI=!0),d=b.getContext("2d"),c.countdownTimer&&a.cancel(c.countdownTimer),c.countdownTimer=a(function(){var a;return c.activeCountdown=!1,a=window.document.getElementById("ng-camera-feed"),d.drawImage(a,0,0,c.width,c.height),null!=c.overlaySrc?c.addFrame(d,c.overlaySrc,function(){return c.$apply(function(){return c.media=b.toDataURL("image/jpeg")}),null!=c.captureCallback?c.captureCallback(c.media):void 0}):(c.media=b.toDataURL("image/jpeg"),null!=c.captureCallback&&c.captureCallback(c.media)),c.hideUI=!1},f+1e3),c.countdownText=parseInt(c.countdown),e=setInterval(function(){return c.$apply(function(){var a;return a=parseInt(c.countdownText)-1,0===a?(c.countdownText=null!=c.captureMessage?c.captureMessage:"GO!",clearInterval(e)):c.countdownText=a})},1e3)),!1},c.addFrame=function(a,b,d){var e;return null==d&&(d=!1),e=new Image,e.onload=function(){return a.drawImage(e,0,0,c.width,c.height),d?d(a):void 0},e.crossOrigin="",e.src=b},c.$watch("media",function(a){return null!=a?c.packagedMedia=c.media.replace(/^data:image\/\w+;base64,/,""):void 0}),c.$watch("overlaySrc",function(a){var b;return null!=c.overlaySrc?(c.isLoaded=!1,b=new Image,b.crossOrigin="",b.src=a,b.onload=function(){return c.$apply(function(){return c.isLoaded=!0})}):c.isLoaded=!0}),c.$watch("enabled",function(a,b){if(a){if(!b)return c.enableCamera()}else if(null!=b)return c.disableCamera()}),c.$watch("type",function(){switch(c.type){case"photo":if(c.enabled)return c.enableCamera();break;default:if(c.enabled)return c.enableCamera()}})}}}])}).call(this); \ No newline at end of file +(function(){"use strict";angular.module("omr.directives",[]).directive("ngCamera",["$timeout","$sce",function(a,b){return{require:"ngModel",template:'

Loading Camera...

Couldn\'t find a camera to use

{{countdownText}}

',replace:!1,transclude:!0,restrict:"E",scope:{type:"@",media:"=ngModel",width:"@",height:"@",captureWidth:"@",captureHeight:"@",overlaySrc:"=",countdown:"=",captureCallback:"&capture",enabled:"=",captureMessage:"@"},link:function(c){return c.captureWidth||(c.captureWidth=c.width),c.captureHeight||(c.captureHeight=c.height),c.activeCountdown=!1,navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,window.URL=window.URL||window.webkitURL||window.mozURL||window.msURL,c.$on("$destroy",function(){c.stream&&"function"==typeof c.stream.stop&&c.stream.stop()}),c.enableCamera=function(){return navigator.getUserMedia({audio:!1,video:!0},function(a){return c.$apply(function(){return c.stream=a,c.isLoaded=!0,c.videoStream=b.trustAsResourceUrl(window.URL.createObjectURL(a))})},function(){return c.$apply(function(){return c.isLoaded=!0,c.noCamera=!0})})},c.disableCamera=function(){return navigator.getUserMedia({audio:!1,video:!0},function(){return c.$apply(function(){return c.videoStream=""})})},c.$on("CAMERA_PICTURE_REQUIRED",function(){return c.takePicture()}),c.takePicture=function(){var b,d,e,f;return b=window.document.getElementById("ng-photo-canvas"),f=null!=c.countdown?1e3*c.countdown:0,null!=b&&(f>0&&(c.activeCountdown=!0,c.hideUI=!0),d=b.getContext("2d"),c.countdownTimer&&a.cancel(c.countdownTimer),c.countdownTimer=a(function(){var a;return c.activeCountdown=!1,a=window.document.getElementById("ng-camera-feed"),d.drawImage(a,0,0,c.captureWidth,c.captureHeight),null!=c.overlaySrc?c.addFrame(d,c.overlaySrc,function(){return c.$apply(function(){return c.media=b.toDataURL("image/jpeg")}),null!=c.captureCallback?c.captureCallback(c.media):void 0}):(c.media=b.toDataURL("image/jpeg"),null!=c.captureCallback&&c.captureCallback(c.media)),c.hideUI=!1},f+1e3),c.countdownText=c.countdown,e=setInterval(function(){return c.$apply(function(){var a;return a=c.countdownText-1,0===a?(c.countdownText=null!=c.captureMessage?c.captureMessage:"GO!",clearInterval(e)):c.countdownText=a})},1e3)),!1},c.addFrame=function(a,b,d){var e;return null==d&&(d=!1),e=new Image,e.onload=function(){return a.drawImage(e,0,0,c.width,c.height),d?d(a):void 0},e.crossOrigin="",e.src=b},c.$watch("media",function(a){return null!=a?c.packagedMedia=c.media.replace(/^data:image\/\w+;base64,/,""):void 0}),c.$watch("overlaySrc",function(a){var b;return null!=c.overlaySrc?(c.isLoaded=!1,b=new Image,b.crossOrigin="",b.src=a,b.onload=function(){return c.$apply(function(){return c.isLoaded=!0})}):c.isLoaded=!0}),c.$watch("enabled",function(a,b){if(a){if(!b)return c.enableCamera()}else if(null!=b)return c.disableCamera()}),c.$watch("type",function(){switch(c.type){case"photo":if(c.enabled)return c.enableCamera();break;default:if(c.enabled)return c.enableCamera()}})}}}])}).call(this); \ No newline at end of file diff --git a/package.json b/package.json index ed0b399..b27e5b9 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,22 @@ { - "name": "angular-camera", - "version": "0.1.3", - "dependencies": {}, - "devDependencies": { - "grunt": "~0.4.1", - "grunt-cli": "~0.1.7", - "grunt-contrib-copy": "~0.4.0", - "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-coffee": "~0.6.4", - "grunt-contrib-uglify": "~0.2.0", - "grunt-contrib-compass": "~0.1.3", - "grunt-contrib-jshint": "~0.3.0", - "grunt-contrib-clean": "~0.4.0", - "grunt-usemin": "~0.1.10", - "grunt-rev": "~0.1.0", - "grunt-open": "~0.2.0", - "matchdep": "~0.1.1", - "grunt-ngmin": "~0.0.2" - } + "name": "angular-camera", + "version": "0.2.2", + "description": "An Angular directive for easily taking pictures from your webcam", + "dependencies": {}, + "main": "dist/angular-camera.js", + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-clean": "~0.4.0", + "grunt-contrib-coffee": "~0.6.4", + "grunt-contrib-compass": "~1.0", + "grunt-contrib-concat": "~0.5", + "grunt-contrib-copy": "~0.7.0", + "grunt-contrib-jshint": "~0.3.0", + "grunt-contrib-uglify": "~0.6.0", + "grunt-ng-annotate": "^0.7.0", + "grunt-open": "~0.2.0", + "grunt-rev": "~0.1.0", + "grunt-usemin": "~0.1.10", + "matchdep": "~0.1.1" + } } diff --git a/src/directives/angular-camera.coffee b/src/directives/angular-camera.coffee index f4cdbf9..c7582d9 100644 --- a/src/directives/angular-camera.coffee +++ b/src/directives/angular-camera.coffee @@ -12,7 +12,7 @@ angular.module('omr.directives', []) - +
@@ -26,13 +26,18 @@ angular.module('omr.directives', []) media: '=ngModel' width: '@' height: '@' + captureWidth: '@' + captureHeight: '@' overlaySrc: '=' - countdown: '@' + countdown: '=' captureCallback: '&capture' enabled: '=' captureMessage: "@" link: (scope, element, attrs, ngModel) -> - + if !scope.captureWidth + scope.captureWidth = scope.width + if !scope.captureHeight + scope.captureHeight = scope.height scope.activeCountdown = false # Remap common references @@ -71,6 +76,10 @@ angular.module('omr.directives', []) scope.$apply -> scope.videoStream = "" + scope.$on('CAMERA_PICTURE_REQUIRED', () -> + scope.takePicture() + ) + ###* * @description Capture current state of video stream as photo ### @@ -78,7 +87,7 @@ angular.module('omr.directives', []) canvas = window.document.getElementById('ng-photo-canvas') # Get countdown time in seconds from attribute - countdownTime = if scope.countdown? then parseInt(scope.countdown) * 1000 else 0 + countdownTime = if scope.countdown? then scope.countdown * 1000 else 0 # Make sure there's a canvas to work with if canvas? @@ -98,7 +107,8 @@ angular.module('omr.directives', []) # Draw current video feed to canvas (photo source) cameraFeed = window.document.getElementById('ng-camera-feed') - context.drawImage cameraFeed, 0, 0, scope.width, scope.height + + context.drawImage cameraFeed, 0, 0, scope.captureWidth, scope.captureHeight # Add overlay if present if scope.overlaySrc? @@ -114,12 +124,12 @@ angular.module('omr.directives', []) scope.hideUI = false , countdownTime + 1000 # Add extra second for final message - scope.countdownText = parseInt(scope.countdown) + scope.countdownText = scope.countdown # Countdown ticker until photo countdownTick = setInterval -> scope.$apply -> - nextTick = parseInt(scope.countdownText) - 1 + nextTick = scope.countdownText - 1 if nextTick is 0 # Replace zero with better copy scope.countdownText = if scope.captureMessage? then scope.captureMessage else 'GO!'