├── .gitignore ├── package.json ├── src └── kinect.js ├── .eslintrc.json ├── lib └── kinect.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@v.kiniv/kinect-js", 3 | "version": "0.0.7", 4 | "description": "", 5 | "main": "lib/kinect", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/v-kiniv/kinect-js.git" 9 | }, 10 | "scripts": { 11 | "build": "babel src --presets babel-preset-es2015 --plugins transform-class-properties --out-dir lib", 12 | "prebuild": "run-s lint", 13 | "lint": "eslint -c .eslintrc.json *.js src", 14 | "publish": "npm publish --access=public" 15 | }, 16 | "author": "Vasily Kiniv", 17 | "license": "ISC", 18 | "dependencies": { 19 | "events": "^1.1.1" 20 | }, 21 | "devDependencies": { 22 | "babel": "^6.23.0", 23 | "babel-cli": "^6.23.0", 24 | "babel-eslint": "^7.1.1", 25 | "babel-plugin-transform-class-properties": "^6.24.1", 26 | "babel-preset-es2015": "^6.22.0", 27 | "eslint": "^3.16.1", 28 | "eslint-plugin-babel": "^4.0.1", 29 | "npm-run-all": "^4.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/kinect.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | 3 | class Kinect extends EventEmitter { 4 | constructor() { 5 | super(); 6 | this.connected = false; 7 | this.socket = null; 8 | this.timer = null; 9 | this.address = '127.0.0.1'; 10 | this.sensor = { 11 | available: true, 12 | trackedBodies: 0 13 | }; 14 | 15 | this.on('newListener', this._handleNewListener); 16 | this.on('removeListener', this._handleRemoveListener); 17 | } 18 | 19 | connect(address, secure) { 20 | if (address !== undefined) { 21 | this.address = address; 22 | } 23 | if (secure === undefined) { 24 | secure = true; 25 | } 26 | if (this.socket !== null) { 27 | this.socket.close(); 28 | } 29 | 30 | this.socket = new WebSocket(`${secure ? 'wss' : 'ws'}://${this.address}:8181`); 31 | this.socket.binaryType = 'arraybuffer'; 32 | 33 | this.lastAdded = null; 34 | this.lastRemoved = null; 35 | 36 | this.socket.onopen = () => { 37 | clearTimeout(this.timer); 38 | this.timer = null; 39 | 40 | this.connected = true; 41 | this._updateSessionOptions(); 42 | this._updateState(); 43 | }; 44 | 45 | this.socket.onclose = () => { 46 | if (this.socket.readyState === WebSocket.OPEN) { 47 | // Previous connection closed. 48 | return; 49 | } 50 | 51 | this.close(); 52 | 53 | this.timer = setTimeout(() => { 54 | this.connect(); 55 | }, 1000); 56 | 57 | }; 58 | 59 | this.socket.onmessage = msg => { 60 | if (typeof msg.data === 'string') { 61 | const event = JSON.parse(msg.data); 62 | 63 | switch (event.type) { 64 | case 'state': 65 | this._handleStateEvent(event); 66 | break; 67 | case 'bodies': 68 | this._handleBodiesEvent(event); 69 | break; 70 | case 'gesture': 71 | this._handleGestureEvent(event); 72 | break; 73 | default: 74 | break; 75 | } 76 | } 77 | else if (msg.data instanceof ArrayBuffer) { 78 | this._handleStreamEvent(msg.data); 79 | } 80 | }; 81 | } 82 | 83 | close() { 84 | this.connected = false; 85 | this.sensor.available = false; 86 | this.sensor.trackedBodies = 0; 87 | this._updateState(); 88 | 89 | if (this.socket !== null) { 90 | this.socket.onmessage = null; 91 | this.socket.onclose = null; 92 | this.socket.close(); 93 | this.socket = null; 94 | } 95 | } 96 | 97 | /* Private methods */ 98 | _handleNewListener = event => { 99 | this.lastAdded = event; 100 | this._updateSessionOptions(); 101 | } 102 | 103 | _handleRemoveListener = event => { 104 | this.lastRemoved = event; 105 | this._updateSessionOptions(); 106 | } 107 | 108 | _sendServerEvent(eventType, data) { 109 | const event = { Type: eventType, Data: JSON.stringify(data) }; 110 | this.socket.send(JSON.stringify(event)); 111 | } 112 | 113 | _updateState() { 114 | const state = { 115 | connected: this.connected, 116 | available: this.sensor.available, 117 | trackedBodies: this.sensor.trackedBodies 118 | }; 119 | this.emit('state', state); 120 | } 121 | 122 | _listenersCount(event) { 123 | let count = this.listenerCount(event); 124 | if (this.lastAdded !== null && event === this.lastAdded) { 125 | count++; 126 | this.lastAdded = null; 127 | } 128 | if (this.lastRemoved !== null && event === this.lastAdded) { 129 | count--; 130 | this.lastRemoved = null; 131 | } 132 | return count; 133 | } 134 | 135 | _updateSessionOptions() { 136 | const config = { 137 | GestureEvents: this._listenersCount('gesture') > 0, 138 | BodyEvents: this._listenersCount('bodies') > 0, 139 | DepthEvents: this._listenersCount('depth') > 0 140 | }; 141 | 142 | if (this.connected) { 143 | this._sendServerEvent('SessionConfig', config); 144 | } 145 | } 146 | 147 | /* Server event handlers */ 148 | _handleStateEvent(event) { 149 | this.sensor.available = event.state.available; 150 | this.sensor.trackedBodies = event.state.trackedBodies; 151 | this._updateState(); 152 | } 153 | 154 | _handleBodiesEvent(event) { 155 | const bodies = []; 156 | for (let i = 0; i < event.bodies.length; i++) { 157 | bodies.push(new Body(event.bodies[i])); 158 | } 159 | this.emit('bodies', bodies, event.floorClipPlane); 160 | } 161 | 162 | _handleGestureEvent(event) { 163 | const { gesture, body } = event; 164 | this.emit('gesture', gesture, body); 165 | } 166 | 167 | _handleStreamEvent(data) { 168 | const desc = new Uint16Array(data, 0, 10); 169 | 170 | if (desc[0] === Kinect.StreamType.Depth) { 171 | const frameDesc = {width: desc[1], height: desc[2], minDistance: desc[3], maxDistance: desc[4]}; 172 | this.emit('depth', new Uint16Array(data, 10), frameDesc); 173 | } 174 | } 175 | } 176 | 177 | Kinect.StreamType = Object.freeze({ 178 | 'IR': 0, 179 | 'Depth': 1, 180 | 'Color': 2 181 | }); 182 | 183 | Kinect.JointType = Object.freeze({ 184 | 0: 'SpineBase', 185 | 1: 'SpineMid', 186 | 2: 'Neck', 187 | 3: 'Head', 188 | 4: 'ShoulderLeft', 189 | 5: 'ElbowLeft', 190 | 6: 'WristLeft', 191 | 7: 'HandLeft', 192 | 8: 'ShoulderRight', 193 | 9: 'ElbowRight', 194 | 10: 'WristRight', 195 | 11: 'HandRight', 196 | 12: 'HipLeft', 197 | 13: 'KneeLeft', 198 | 14: 'AnkleLeft', 199 | 15: 'FootLeft', 200 | 16: 'HipRight', 201 | 17: 'KneeRight', 202 | 18: 'AnkleRight', 203 | 19: 'FootRight', 204 | 20: 'SpineShoulder', 205 | 21: 'HandTipLeft', 206 | 22: 'ThumbLeft', 207 | 23: 'HandTipRight', 208 | 24: 'ThumbRight' 209 | }); 210 | 211 | Kinect.HandState = Object.freeze({ 212 | 0: 'Unknown', 213 | 1: 'NotTracked', 214 | 2: 'Open', 215 | 3: 'Closed', 216 | 4: 'Lasso' 217 | }); 218 | 219 | Kinect.TrackingConfidence = Object.freeze({ 220 | 0: 'Hight', 221 | 1: 'Low' 222 | }); 223 | 224 | Kinect.TrackingState = Object.freeze({ 225 | 0: 'NotTracked', 226 | 1: 'Inferred', 227 | 2: 'Tracked' 228 | }); 229 | 230 | class Body { 231 | constructor(compactBody) { 232 | this.trackingId = compactBody.TI; 233 | this.isClosest = compactBody.IC; 234 | this.handLeftConfidence = Kinect.TrackingConfidence[compactBody.HLC]; 235 | this.handLeftState = Kinect.HandState[compactBody.HLS]; 236 | this.handRightConfidence = Kinect.TrackingConfidence[compactBody.HRC]; 237 | this.handRightState = Kinect.HandState[compactBody.HRS]; 238 | this.leanTrackingState = Kinect.TrackingState[compactBody.LTS]; 239 | this.lean = compactBody.LN; 240 | this.skeleton = {}; 241 | 242 | for (let i = 0; i < compactBody.JN.length; i++) { 243 | this.skeleton[Kinect.JointType[i]] = new Joint(compactBody.JN[i]); 244 | } 245 | } 246 | } 247 | 248 | class Joint { 249 | constructor(compactJoint) { 250 | this.pos = compactJoint.P; 251 | this.orient = compactJoint.O; 252 | this.state = Kinect.TrackingState[compactJoint.S]; 253 | } 254 | } 255 | 256 | export default new Kinect(); 257 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "plugins": [], 5 | "extends": [ 6 | "eslint:recommended" 7 | ], 8 | "env": { 9 | "browser": true, 10 | "es6": true, 11 | "jasmine": true, 12 | "node": true 13 | }, 14 | "ecmaFeatures": { 15 | "jsx": true, 16 | "modules": true 17 | }, 18 | "globals": { 19 | "firebase": true, 20 | "sinon": true 21 | }, 22 | "rules": { 23 | "array-bracket-spacing": 0, 24 | "array-callback-return": 0, 25 | "arrow-body-style": 0, 26 | "arrow-parens": [ 27 | 2, 28 | "as-needed" 29 | ], 30 | "arrow-spacing": [ 31 | 2, 32 | { 33 | "before": true, 34 | "after": true 35 | } 36 | ], 37 | "accessor-pairs": 0, 38 | "block-scoped-var": 0, 39 | "block-spacing": 2, 40 | "brace-style": [ 41 | 2, 42 | "stroustrup", 43 | { 44 | "allowSingleLine": true 45 | } 46 | ], 47 | "callback-return": 0, 48 | "camelcase": [ 49 | 2, 50 | { 51 | "properties": "always" 52 | } 53 | ], 54 | "comma-dangle": 2, 55 | "comma-spacing": 2, 56 | "comma-style": [ 57 | 2, 58 | "last" 59 | ], 60 | "complexity": [ 61 | 0, 62 | 10 63 | ], 64 | "computed-property-spacing": [ 65 | 2, 66 | "never" 67 | ], 68 | "consistent-return": 0, 69 | "consistent-this": 0, 70 | "constructor-super": 2, 71 | "curly": [ 72 | 2, 73 | "multi-line" 74 | ], 75 | "default-case": 2, 76 | "dot-location": [ 77 | 2, 78 | "property" 79 | ], 80 | "dot-notation": [ 81 | 2, 82 | { 83 | "allowKeywords": true 84 | } 85 | ], 86 | "eol-last": 2, 87 | "eqeqeq": 2, 88 | "func-names": 0, 89 | "func-style": [ 90 | 2, 91 | "declaration", 92 | { 93 | "allowArrowFunctions": true 94 | } 95 | ], 96 | "generator-star-spacing": [ 97 | 2, 98 | { 99 | "before": false, 100 | "after": true 101 | } 102 | ], 103 | "guard-for-in": 2, 104 | "global-require": 0, 105 | "handle-callback-err": 0, 106 | "id-blacklist": 0, 107 | "id-length": 0, 108 | "id-match": 0, 109 | "indent": [ 110 | 2, 111 | 2, 112 | { 113 | "SwitchCase": 1, 114 | "VariableDeclarator": 2 115 | } 116 | ], 117 | "init-declarations": 0, 118 | "jsx-quotes": 1, 119 | "key-spacing": [ 120 | 2, 121 | { 122 | "beforeColon": false, 123 | "afterColon": true 124 | } 125 | ], 126 | "keyword-spacing": 2, 127 | "linebreak-style": 0, 128 | "lines-around-comment": 0, 129 | "max-depth": 0, 130 | "max-len": 0, 131 | "max-nested-callbacks": 0, 132 | "max-params": 0, 133 | "max-statements": 0, 134 | "max-statements-per-line": 0, 135 | "new-cap": 2, 136 | "new-parens": 2, 137 | "newline-after-var": 0, 138 | "newline-before-return": 0, 139 | "newline-per-chained-call": 0, 140 | "no-alert": 2, 141 | "no-array-constructor": 2, 142 | "no-bitwise": 0, 143 | "no-caller": 2, 144 | "no-case-declarations": 2, 145 | "no-catch-shadow": 0, 146 | "no-class-assign": 2, 147 | "no-cond-assign": 2, 148 | "no-confusing-arrow": 0, 149 | "no-console": 2, 150 | "no-const-assign": 2, 151 | "no-constant-condition": 2, 152 | "no-continue": 0, 153 | "no-control-regex": 2, 154 | "no-debugger": 2, 155 | "no-delete-var": 2, 156 | "no-div-regex": 0, 157 | "no-dupe-args": 2, 158 | "no-dupe-class-members": 2, 159 | "no-dupe-keys": 2, 160 | "no-duplicate-case": 2, 161 | "no-duplicate-imports": 2, 162 | "no-else-return": 0, 163 | "no-empty": 2, 164 | "no-empty-character-class": 2, 165 | "no-empty-function": 0, 166 | "no-empty-pattern": 2, 167 | "no-eq-null": 0, 168 | "no-eval": 2, 169 | "no-ex-assign": 2, 170 | "no-extend-native": 2, 171 | "no-extra-bind": 2, 172 | "no-extra-boolean-cast": 2, 173 | "no-extra-label": 0, 174 | "no-extra-parens": 0, 175 | "no-extra-semi": 2, 176 | "no-fallthrough": 2, 177 | "no-floating-decimal": 2, 178 | "no-func-assign": 2, 179 | "no-implicit-coercion": 0, 180 | "no-implicit-globals": 0, 181 | "no-implied-eval": 2, 182 | "no-inline-comments": 0, 183 | "no-inner-declarations": 2, 184 | "no-invalid-regexp": 2, 185 | "no-invalid-this": 0, 186 | "no-irregular-whitespace": 2, 187 | "no-iterator": 2, 188 | "no-label-var": 2, 189 | "no-labels": 2, 190 | "no-lone-blocks": 0, 191 | "no-lonely-if": 0, 192 | "no-loop-func": 0, 193 | "no-magic-numbers": 0, 194 | "no-mixed-requires": 2, 195 | "no-mixed-spaces-and-tabs": [ 196 | 2, 197 | false 198 | ], 199 | "no-multi-spaces": 2, 200 | "no-multi-str": 2, 201 | "no-multiple-empty-lines": 0, 202 | "no-native-reassign": 2, 203 | "no-negated-condition": 0, 204 | "no-negated-in-lhs": 2, 205 | "no-nested-ternary": 2, 206 | "no-new": 2, 207 | "no-new-func": 2, 208 | "no-new-object": 2, 209 | "no-new-require": 2, 210 | "no-new-symbol": 2, 211 | "no-new-wrappers": 2, 212 | "no-obj-calls": 2, 213 | "no-octal": 2, 214 | "no-octal-escape": 2, 215 | "no-param-reassign": 0, 216 | "no-path-concat": 2, 217 | "no-plusplus": 0, 218 | "no-process-env": 0, 219 | "no-process-exit": 2, 220 | "no-proto": 2, 221 | "no-redeclare": 2, 222 | "no-regex-spaces": 2, 223 | "no-restricted-globals": 0, 224 | "no-restricted-imports": 0, 225 | "no-restricted-modules": 0, 226 | "no-restricted-syntax": 0, 227 | "no-return-assign": 0, 228 | "no-script-url": 2, 229 | "no-self-assign": 2, 230 | "no-self-compare": 0, 231 | "no-sequences": 2, 232 | "no-shadow": 0, 233 | "no-shadow-restricted-names": 2, 234 | "no-spaced-func": 2, 235 | "no-sparse-arrays": 2, 236 | "no-sync": 0, 237 | "no-ternary": 0, 238 | "no-trailing-spaces": 2, 239 | "no-this-before-super": 2, 240 | "no-throw-literal": 0, 241 | "no-undef": 2, 242 | "no-undef-init": 2, 243 | "no-undefined": 0, 244 | "no-unexpected-multiline": 2, 245 | "no-underscore-dangle": 0, 246 | "no-unmodified-loop-condition": 2, 247 | "no-unneeded-ternary": 0, 248 | "no-unreachable": 2, 249 | "no-unsafe-finally": 0, 250 | "no-unused-expressions": 2, 251 | "no-unused-labels": 2, 252 | "no-unused-vars": [ 253 | 2, 254 | { 255 | "vars": "all", 256 | "args": "after-used" 257 | } 258 | ], 259 | "no-use-before-define": 0, 260 | "no-useless-call": 0, 261 | "no-useless-computed-key": 0, 262 | "no-useless-concat": 2, 263 | "no-useless-constructor": 0, 264 | "no-useless-escape": 0, 265 | "no-void": 0, 266 | "no-var": 0, 267 | "no-warning-comments": 0, 268 | "no-whitespace-before-property": 2, 269 | "no-with": 2, 270 | "object-curly-spacing": [ 271 | 0, 272 | "never" 273 | ], 274 | "object-shorthand": 0, 275 | "one-var": 0, 276 | "one-var-declaration-per-line": 0, 277 | "operator-assignment": 0, 278 | "operator-linebreak": 0, 279 | "padded-blocks": 0, 280 | "prefer-arrow-callback": 0, 281 | "prefer-const": 0, 282 | "prefer-reflect": 0, 283 | "prefer-rest-params": 0, 284 | "prefer-spread": 0, 285 | "prefer-template": 0, 286 | "quote-props": 0, 287 | "quotes": [ 288 | 2, 289 | "single" 290 | ], 291 | "radix": 0, 292 | "require-jsdoc": 0, 293 | "require-yield": 0, 294 | "semi": 2, 295 | "semi-spacing": [ 296 | 2, 297 | { 298 | "before": false, 299 | "after": true 300 | } 301 | ], 302 | "sort-imports": 0, 303 | "sort-vars": 0, 304 | "space-before-blocks": 2, 305 | "space-before-function-paren": [ 306 | 2, 307 | "never" 308 | ], 309 | "space-in-parens": 0, 310 | "space-infix-ops": 2, 311 | "space-unary-ops": [ 312 | 2, 313 | { 314 | "words": true, 315 | "nonwords": false 316 | } 317 | ], 318 | "spaced-comment": [ 319 | 2, 320 | "always", 321 | { 322 | "exceptions": [ 323 | "-", 324 | "=" 325 | ] 326 | } 327 | ], 328 | "strict": 0, 329 | "template-curly-spacing": 0, 330 | "use-isnan": 2, 331 | "valid-jsdoc": [ 332 | 2, 333 | { 334 | "prefer": { 335 | "return": "returns" 336 | } 337 | } 338 | ], 339 | "valid-typeof": 2, 340 | "vars-on-top": 0, 341 | "wrap-iife": 2, 342 | "wrap-regex": 0, 343 | "yield-star-spacing": 0, 344 | "yoda": [ 345 | 2, 346 | "never" 347 | ] 348 | } 349 | } -------------------------------------------------------------------------------- /lib/kinect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _events = require('events'); 10 | 11 | var _events2 = _interopRequireDefault(_events); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 18 | 19 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20 | 21 | var Kinect = function (_EventEmitter) { 22 | _inherits(Kinect, _EventEmitter); 23 | 24 | function Kinect() { 25 | _classCallCheck(this, Kinect); 26 | 27 | var _this = _possibleConstructorReturn(this, (Kinect.__proto__ || Object.getPrototypeOf(Kinect)).call(this)); 28 | 29 | _this._handleNewListener = function (event) { 30 | _this.lastAdded = event; 31 | _this._updateSessionOptions(); 32 | }; 33 | 34 | _this._handleRemoveListener = function (event) { 35 | _this.lastRemoved = event; 36 | _this._updateSessionOptions(); 37 | }; 38 | 39 | _this.connected = false; 40 | _this.socket = null; 41 | _this.timer = null; 42 | _this.address = '127.0.0.1'; 43 | _this.sensor = { 44 | available: true, 45 | trackedBodies: 0 46 | }; 47 | 48 | _this.on('newListener', _this._handleNewListener); 49 | _this.on('removeListener', _this._handleRemoveListener); 50 | return _this; 51 | } 52 | 53 | _createClass(Kinect, [{ 54 | key: 'connect', 55 | value: function connect(address, secure) { 56 | var _this2 = this; 57 | 58 | if (address !== undefined) { 59 | this.address = address; 60 | } 61 | if (secure === undefined) { 62 | secure = true; 63 | } 64 | if (this.socket !== null) { 65 | this.socket.close(); 66 | } 67 | 68 | this.socket = new WebSocket((secure ? 'wss' : 'ws') + '://' + this.address + ':8181'); 69 | this.socket.binaryType = 'arraybuffer'; 70 | 71 | this.lastAdded = null; 72 | this.lastRemoved = null; 73 | 74 | this.socket.onopen = function () { 75 | clearTimeout(_this2.timer); 76 | _this2.timer = null; 77 | 78 | _this2.connected = true; 79 | _this2._updateSessionOptions(); 80 | _this2._updateState(); 81 | }; 82 | 83 | this.socket.onclose = function () { 84 | if (_this2.socket.readyState === WebSocket.OPEN) { 85 | // Previous connection closed. 86 | return; 87 | } 88 | 89 | _this2.close(); 90 | 91 | _this2.timer = setTimeout(function () { 92 | _this2.connect(); 93 | }, 1000); 94 | }; 95 | 96 | this.socket.onmessage = function (msg) { 97 | if (typeof msg.data === 'string') { 98 | var event = JSON.parse(msg.data); 99 | 100 | switch (event.type) { 101 | case 'state': 102 | _this2._handleStateEvent(event); 103 | break; 104 | case 'bodies': 105 | _this2._handleBodiesEvent(event); 106 | break; 107 | case 'gesture': 108 | _this2._handleGestureEvent(event); 109 | break; 110 | default: 111 | break; 112 | } 113 | } else if (msg.data instanceof ArrayBuffer) { 114 | _this2._handleStreamEvent(msg.data); 115 | } 116 | }; 117 | } 118 | }, { 119 | key: 'close', 120 | value: function close() { 121 | this.connected = false; 122 | this.sensor.available = false; 123 | this.sensor.trackedBodies = 0; 124 | this._updateState(); 125 | 126 | if (this.socket !== null) { 127 | this.socket.onmessage = null; 128 | this.socket.onclose = null; 129 | this.socket.close(); 130 | this.socket = null; 131 | } 132 | } 133 | 134 | /* Private methods */ 135 | 136 | }, { 137 | key: '_sendServerEvent', 138 | value: function _sendServerEvent(eventType, data) { 139 | var event = { Type: eventType, Data: JSON.stringify(data) }; 140 | this.socket.send(JSON.stringify(event)); 141 | } 142 | }, { 143 | key: '_updateState', 144 | value: function _updateState() { 145 | var state = { 146 | connected: this.connected, 147 | available: this.sensor.available, 148 | trackedBodies: this.sensor.trackedBodies 149 | }; 150 | this.emit('state', state); 151 | } 152 | }, { 153 | key: '_listenersCount', 154 | value: function _listenersCount(event) { 155 | var count = this.listenerCount(event); 156 | if (this.lastAdded !== null && event === this.lastAdded) { 157 | count++; 158 | this.lastAdded = null; 159 | } 160 | if (this.lastRemoved !== null && event === this.lastAdded) { 161 | count--; 162 | this.lastRemoved = null; 163 | } 164 | return count; 165 | } 166 | }, { 167 | key: '_updateSessionOptions', 168 | value: function _updateSessionOptions() { 169 | var config = { 170 | GestureEvents: this._listenersCount('gesture') > 0, 171 | BodyEvents: this._listenersCount('bodies') > 0, 172 | DepthEvents: this._listenersCount('depth') > 0 173 | }; 174 | 175 | if (this.connected) { 176 | this._sendServerEvent('SessionConfig', config); 177 | } 178 | } 179 | 180 | /* Server event handlers */ 181 | 182 | }, { 183 | key: '_handleStateEvent', 184 | value: function _handleStateEvent(event) { 185 | this.sensor.available = event.state.available; 186 | this.sensor.trackedBodies = event.state.trackedBodies; 187 | this._updateState(); 188 | } 189 | }, { 190 | key: '_handleBodiesEvent', 191 | value: function _handleBodiesEvent(event) { 192 | var bodies = []; 193 | for (var i = 0; i < event.bodies.length; i++) { 194 | bodies.push(new Body(event.bodies[i])); 195 | } 196 | this.emit('bodies', bodies, event.floorClipPlane); 197 | } 198 | }, { 199 | key: '_handleGestureEvent', 200 | value: function _handleGestureEvent(event) { 201 | var gesture = event.gesture, 202 | body = event.body; 203 | 204 | this.emit('gesture', gesture, body); 205 | } 206 | }, { 207 | key: '_handleStreamEvent', 208 | value: function _handleStreamEvent(data) { 209 | var desc = new Uint16Array(data, 0, 10); 210 | 211 | if (desc[0] === Kinect.StreamType.Depth) { 212 | var frameDesc = { width: desc[1], height: desc[2], minDistance: desc[3], maxDistance: desc[4] }; 213 | this.emit('depth', new Uint16Array(data, 10), frameDesc); 214 | } 215 | } 216 | }]); 217 | 218 | return Kinect; 219 | }(_events2.default); 220 | 221 | Kinect.StreamType = Object.freeze({ 222 | 'IR': 0, 223 | 'Depth': 1, 224 | 'Color': 2 225 | }); 226 | 227 | Kinect.JointType = Object.freeze({ 228 | 0: 'SpineBase', 229 | 1: 'SpineMid', 230 | 2: 'Neck', 231 | 3: 'Head', 232 | 4: 'ShoulderLeft', 233 | 5: 'ElbowLeft', 234 | 6: 'WristLeft', 235 | 7: 'HandLeft', 236 | 8: 'ShoulderRight', 237 | 9: 'ElbowRight', 238 | 10: 'WristRight', 239 | 11: 'HandRight', 240 | 12: 'HipLeft', 241 | 13: 'KneeLeft', 242 | 14: 'AnkleLeft', 243 | 15: 'FootLeft', 244 | 16: 'HipRight', 245 | 17: 'KneeRight', 246 | 18: 'AnkleRight', 247 | 19: 'FootRight', 248 | 20: 'SpineShoulder', 249 | 21: 'HandTipLeft', 250 | 22: 'ThumbLeft', 251 | 23: 'HandTipRight', 252 | 24: 'ThumbRight' 253 | }); 254 | 255 | Kinect.HandState = Object.freeze({ 256 | 0: 'Unknown', 257 | 1: 'NotTracked', 258 | 2: 'Open', 259 | 3: 'Closed', 260 | 4: 'Lasso' 261 | }); 262 | 263 | Kinect.TrackingConfidence = Object.freeze({ 264 | 0: 'Hight', 265 | 1: 'Low' 266 | }); 267 | 268 | Kinect.TrackingState = Object.freeze({ 269 | 0: 'NotTracked', 270 | 1: 'Inferred', 271 | 2: 'Tracked' 272 | }); 273 | 274 | var Body = function Body(compactBody) { 275 | _classCallCheck(this, Body); 276 | 277 | this.trackingId = compactBody.TI; 278 | this.isClosest = compactBody.IC; 279 | this.handLeftConfidence = Kinect.TrackingConfidence[compactBody.HLC]; 280 | this.handLeftState = Kinect.HandState[compactBody.HLS]; 281 | this.handRightConfidence = Kinect.TrackingConfidence[compactBody.HRC]; 282 | this.handRightState = Kinect.HandState[compactBody.HRS]; 283 | this.leanTrackingState = Kinect.TrackingState[compactBody.LTS]; 284 | this.lean = compactBody.LN; 285 | this.skeleton = {}; 286 | 287 | for (var i = 0; i < compactBody.JN.length; i++) { 288 | this.skeleton[Kinect.JointType[i]] = new Joint(compactBody.JN[i]); 289 | } 290 | }; 291 | 292 | var Joint = function Joint(compactJoint) { 293 | _classCallCheck(this, Joint); 294 | 295 | this.pos = compactJoint.P; 296 | this.orient = compactJoint.O; 297 | this.state = Kinect.TrackingState[compactJoint.S]; 298 | }; 299 | 300 | exports.default = new Kinect(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kinect.js 2 | It is a communication bridge between Kinect and Web. 3 | kinect.js receive data from Kinect through Host Application(Server) deployed on Windows machine. 4 | The server listens for incoming connections on default `8181` port on all interfaces. 5 | 6 | # Setup 7 | Include kinect.js library: 8 | ``` 9 | #!html 10 | 11 | 12 | ``` 13 | 14 | Create client instance and call connect method: 15 | ``` 16 | #!js 17 | var kinect = new Kinect(); 18 | kinect.connect(); // 127.0.0.1 by default 19 | ``` 20 | *** 21 | To be able to use helper module that provide connection state monitor, include helper.js: 22 | ``` 23 | 24 | #!html 25 | 26 | 27 | ``` 28 | Add state view container to body: 29 | ``` 30 | #!html 31 |
32 | 33 | 34 | 35 | ``` 36 | 37 | Pass client instance to KinectHelper constructor: 38 | ``` 39 | #!js 40 | var kinect = new Kinect(); 41 | var kinect_helper = new KinectHelper(kinect); 42 | kinect.connect("192.168.0.1"); 43 | ``` 44 | 45 | # Events 46 | 47 | | Type | Description 48 | | ------------ | ------------------ 49 | | [`state`](#markdown-header-state-event) | Connection or sensor state has changed. 50 | | [`gesture`](#markdown-header-gesture-event) | Gesture detected. 51 | | [`bodies`](#markdown-header-bodies-event) | Tracked bodies data updated. 52 | 53 | ## Event subscription 54 | To start receiving Kinect events, register event listeners: 55 | ``` 56 | #!js 57 | var kinect = new Kinect(); 58 | ... 59 | // Listen for gesture events 60 | kinect.addEventListener("gesture", event => { 61 | if(event.gesture == "Swipe_Left") { 62 | // Do something 63 | } 64 | }); 65 | 66 | // Listen for state events 67 | kinect.addEventListener("state", event => { 68 | if(event.connected) { 69 | // Do something 70 | } 71 | }); 72 | ``` 73 | 74 | The server is aware of active event listeners and sends only needed events to the browser. For instance, if you do not need user skeleton data, do not leave empty [`bodies`](#markdown-header-bodies-event) event listener, remove addEventListener statement. You can also unsubscribe from the event at runtime: 75 | ``` 76 | #!js 77 | kinect.removeEventListener("bodies", func_ref); 78 | 79 | ``` 80 | 81 | ## State event 82 | ### Sample 83 | ``` 84 | #!json 85 | { 86 | "connected": true, 87 | "available": true, 88 | "trackedBodies": 2 89 | } 90 | ``` 91 | 92 | ### Members 93 | 94 | | Member | Description 95 | |---------|----------- 96 | | `connected` | Client - Host WebSockets connection state. 97 | | `available` | Host - Kinect sensor connection state. 98 | | `trackedBodies` | Number of bodies tracked by the sensor at the current time. 99 | 100 | 101 | ## Gesture event 102 | ### Sample 103 | ``` 104 | #!json 105 | { 106 | "gesture": "ThumbUp", 107 | "body": { 108 | "isClosest": true, 109 | "trackingId": 72057594037930130 110 | } 111 | } 112 | ``` 113 | ### Members 114 | 115 | | Member | Description 116 | |-----------|------------ 117 | | `gesture` | Gesture name as stated in gestures database. 118 | | `body` | Compact version of [`Body`](#markdown-header-body) object. 119 | 120 | ### Gestures list 121 | Current default database contain basic gestures: 122 | 123 | + `Swipe_Left` 124 | + `Swipe_Right` 125 | + `Tap` 126 | + `HandsUp` 127 | + `ThumbUp` 128 | 129 | > List of gestures above is subject to change as it is related to Kinect gestures database 130 | > used by the host application. 131 | 132 | ## Bodies event 133 | ### Sample 134 | ``` 135 | #!js 136 | { 137 | "bodies": [{ 138 | "trackingId": 72057594037928860, 139 | "isClosest:": true, 140 | "handLeftConfidence": "High", 141 | "handLeftState": "Open", 142 | "handRightConfidence": "High", 143 | "handRightState": "Lasso", 144 | "leanTrackingState": "Tracked", 145 | "lean": { 146 | "x": 0.0, 147 | "y": 0.0 148 | }, 149 | "skeleton": { 150 | "Head": { 151 | "pos": { 152 | "x": 0.0, 153 | "y": 0.0, 154 | "z": 0.0, 155 | }, 156 | "orient": { 157 | "w": 0.0, 158 | "x": 0.0, 159 | "y": 0.0, 160 | "z": 0.0, 161 | }, 162 | "state": "Tracked" 163 | }, 164 | "Neck": { 165 | "pos": { 166 | "x": 0.0, 167 | "y": 0.0, 168 | "z": 0.0, 169 | }, 170 | "orient": { 171 | "w": 0.0, 172 | "x": 0.0, 173 | "y": 0.0, 174 | "z": 0.0, 175 | }, 176 | "state": "Inferred" 177 | }, 178 | // ... 25 joints total 179 | } 180 | }, 181 | // ... up to 6 bodies total 182 | ], 183 | "floorClipPlane": { 184 | "w": 0.0, 185 | "x": 0.0, 186 | "y": 0.0, 187 | "z": 0.0, 188 | } 189 | } 190 | ``` 191 | 192 | ### Members 193 | | Member | Description 194 | |---------|------------ 195 | | bodies | Array of [Body](#markdown-header-body) objects, each represent tracked user skeleton. 196 | | floorClipPlane | [Vector4](#markdown-header-vector4) object. Gets the floor clip plane of the body frame in hessian normal form. The (x,y,z) components are a unit vector indicating the normal of the plane, and w is the distance from the plane to the origin in meters. | 197 | 198 | 199 | # Reference 200 | 201 | ## Body 202 | ###### object 203 | 204 | ### Properties 205 | | Member | Type | Description 206 | |---------------------|-----------------------------------------------------------|--------------------- 207 | | trackingId | long integer | Unique body ID. 208 | | isClosest | boolean | Indicate whenever body is closest to the sensor. 209 | | handLeftConfidence | [TrackingConfidence](#markdown-header-trackingconfidence) | Left hand tracking confidence. 210 | | handLeftState | [HandState](#markdown-header-handstate) | Left hand state. 211 | | handRightConfidence | [TrackingConfidence](#markdown-header-trackingconfidence) | Right hand tracking confidence. 212 | | handRightState | [HandState](#markdown-header-handstate) | Right hand state. 213 | | leanTrackingState | [TrackingState](#markdown-header-trackingstate) | Lean tracking state. 214 | | lean | [Point](#markdown-header-point) | The lean vector of the body. 215 | | skeleton | [Skeleton](#markdown-header-skeleton) | Array of joints. 216 | 217 | ## Skeleton 218 | ###### object 219 | Represent joint position and orientation. 220 | 221 | Member | Type | Description 222 | ----------------|---------------------------------|------------------------- 223 | `AnkleLeft` | [Joint](#markdown-header-joint) | Left ankle 224 | `AnkleRight` | [Joint](#markdown-header-joint) | Right ankle 225 | `ElbowLeft` | [Joint](#markdown-header-joint) | Left elbow 226 | `ElbowRight` | [Joint](#markdown-header-joint) | Right elbow 227 | `FootLeft` | [Joint](#markdown-header-joint) | Left foot 228 | `FootRight` | [Joint](#markdown-header-joint) | Right foot 229 | `HandLeft` | [Joint](#markdown-header-joint) | Left hand 230 | `HandRight` | [Joint](#markdown-header-joint) | Right hand 231 | `HandTipLeft` | [Joint](#markdown-header-joint) | Tip of the left hand 232 | `HandTipRight`| [Joint](#markdown-header-joint) | Tip of the right hand 233 | `Head` | [Joint](#markdown-header-joint) | Head 234 | `HipLeft` | [Joint](#markdown-header-joint) | Left hip 235 | `HipRight` | [Joint](#markdown-header-joint) | Right hip 236 | `KneeLeft` | [Joint](#markdown-header-joint) | Left knee 237 | `KneeRight` | [Joint](#markdown-header-joint) | Right knee 238 | `Neck` | [Joint](#markdown-header-joint) | Neck 239 | `ShoulderLeft` | [Joint](#markdown-header-joint) | Left shoulder 240 | `ShoulderRight` | [Joint](#markdown-header-joint) | Right shoulder 241 | `SpineBase` | [Joint](#markdown-header-joint) | Base of the spine 242 | `SpineMid` | [Joint](#markdown-header-joint) | Middle of the spine 243 | `SpineShoulder` | [Joint](#markdown-header-joint) | Spine 244 | `ThumbLeft` | [Joint](#markdown-header-joint) | Left thumb 245 | `ThumbRight` | [Joint](#markdown-header-joint) | Right thumb 246 | `WristLeft` | [Joint](#markdown-header-joint) | Left wrist 247 | `WristRight` | [Joint](#markdown-header-joint) | Right wrist 248 | 249 | ## Joint 250 | ###### object 251 | Represent joint position and orientation. 252 | 253 | Member | Type | Description 254 | ---------------|---------------------------------------------------------|------------------- 255 | `pos` | [CameraSpacePoint](#markdown-header-cameraspacepoint) | Joint position. 256 | `orient` | [Vector4](#markdown-header-vector4) | Joint orientation. 257 | `state` | [TrackingState](#markdown-header-trackingstate) | Joint tracking state. 258 | 259 | ## Point 260 | ###### object 261 | Represents point in 2D space. 262 | 263 | Member | Type | Description 264 | ---------|--------|--------------------- 265 | `x` | double | The x-coordinate. 266 | `y` | double | The y-coordinate. 267 | 268 | ## CameraSpacePoint 269 | ###### object 270 | Represents point in camera 3D space. 271 | 272 | Camera space refers to the 3D coordinate system used by Kinect. The coordinate system is defined as follows: 273 | 274 | - The origin (x=0, y=0, z=0) is located at the center of the IR sensor on Kinect 275 | - X grows to the sensor’s left 276 | - Y grows up (note that this direction is based on the sensor’s tilt) 277 | - Z grows out in the direction the sensor is facing 278 | - 1 unit = 1 meter 279 | 280 | Member | Type | Description 281 | --------|--------|--------------------- 282 | `x` | double | The X coordinate of the point, in meters. 283 | `y` | double | The Y coordinate of the point, in meters. 284 | `z` | double | The Z coordinate of the point, in meters. 285 | 286 | ## Vector4 287 | ###### object 288 | The Vector4 structure is a flexible type that is used to represent a four component vector of skeleton or stream (color, depth, infrared) data. This structure is similar to the XMVECTOR structure in the XNA math library. 289 | Represents a 4-element (X,Y,Z,W) vector. 290 | 291 | Member | Type | Description 292 | --------|--------|--------------------- 293 | `x` | double | The x-coordinate. 294 | `y` | double | The y-coordinate. 295 | `z` | double | The z-coordinate. 296 | `w` | double | For the floor plane, the w value is the distance from the plane to the origin. 297 | 298 | 299 | ## HandState 300 | ###### enum 301 | Possible hand states. 302 | 303 | Value | Description 304 | -------------|--------------------------- 305 | `Closed` | The hand is closed. 306 | `Lasso` | The hand is in the lasso state. 307 | `NotTracked` | Hand state is not tracked. 308 | `Open` | The hand is open. 309 | `Unknown` | The state of the hand is unknown. 310 | 311 | ## TrackingConfidence 312 | ###### enum 313 | Value | Description 314 | -----------|--------------------------- 315 | `High` | Fully tracked. 316 | `Low` | Not tracked. 317 | 318 | ## TrackingState 319 | ###### enum 320 | Value | Description 321 | ---------------|--------------------------- 322 | `Inferred` | The joint data is inferred and confidence in the position data is lower than if it were Tracked. 323 | `NotTracked` | The joint data is not tracked and no data is known about this joint. 324 | `Tracked` | The joint data is being tracked and the data can be trusted. --------------------------------------------------------------------------------