diff --git a/raspberry/roberto/views/frontend/routes.py b/raspberry/roberto/views/frontend/routes.py index 4d4c76c..6ce7441 100644 --- a/raspberry/roberto/views/frontend/routes.py +++ b/raspberry/roberto/views/frontend/routes.py @@ -32,4 +32,8 @@ def index(): @frontend_blueprint.route('/gamepad.js') def gamepad_js(): - return render_template('gamepad.js') + return render_template('gamepad.js'), {'Content-Type': 'text/javascript'} + +@frontend_blueprint.route('/virtualjoystick.js') +def virtualjoystick_js(): + return render_template('virtualjoystick.js'), {'Content-Type': 'text/javascript'} diff --git a/raspberry/roberto/views/frontend/templates/gamepad.js b/raspberry/roberto/views/frontend/templates/gamepad.js index d261a32..b0a30bd 100644 --- a/raspberry/roberto/views/frontend/templates/gamepad.js +++ b/raspberry/roberto/views/frontend/templates/gamepad.js @@ -109,6 +109,7 @@ function updateStatus() { axesChanged = true; } } + if (axesChanged) { gp_socket.emit('axes', {0: controller.axes[0], 1: controller.axes[1], 3: controller.axes[3], 4: controller.axes[4]}); } @@ -134,3 +135,64 @@ if (haveEvents) { } else { setInterval(scangamepads, 500); } + +// Virtual Joystick + var joystick = new VirtualJoystick({ + container: document.getElementById('controls'), + mouseSupport: true, + stationaryBase: true, + baseX: 250, + baseY: window.innerHeight-300, + limitStickTravel: true, + stickRadius: 120, + strokeStyle: 'cyan' + }); + + var joystick2 = new VirtualJoystick({ + container: document.getElementById('controls'), + mouseSupport: true, + stationaryBase: true, + baseX: window.innerWidth-250, + baseY: window.innerHeight-300, + limitStickTravel: true, + stickRadius: 120, + strokeStyle: 'orange' + }); + + joystick.addEventListener('touchStartValidation', function(event){ + var touch = event.changedTouches[0]; + if( touch.pageX >= window.innerWidth/2 ) return false; + return true + }); + joystick.addEventListener('mouseStartValidation', function(event){ + if( event.clientX >= window.innerWidth/2 ) return false; + return true + }); + + + joystick2.addEventListener('touchStartValidation', function(event){ + var touch = event.changedTouches[0]; + if( touch.pageX < window.innerWidth/2 ) return false; + return true + }); + joystick2.addEventListener('mouseStartValidation', function(event){ + if( event.clientX < window.innerWidth/2 ) return false; + return true + }); +/* + joystick2.addEventListener('touchStart', function(){ + console.log('fire') + }) +*/ + +setInterval(function(){ + if (joystick && joystick2) { + if (joystick._pressed === true || joystick2._pressed === true) { + var dx = joystick.deltaX() / joystick._stickRadius; + var dy = joystick.deltaY() / joystick._stickRadius; + var dx2 = joystick2.deltaX() / joystick2._stickRadius; + var dy2 = joystick2.deltaY() / joystick2._stickRadius; + gp_socket.emit('axes', {0: dx, 1: dy, 3: dx2, 4: dy2}); + } + } +}, 50); diff --git a/raspberry/roberto/views/frontend/templates/index.html b/raspberry/roberto/views/frontend/templates/index.html index 8cef378..7f9fad1 100644 --- a/raspberry/roberto/views/frontend/templates/index.html +++ b/raspberry/roberto/views/frontend/templates/index.html @@ -3,9 +3,27 @@ - Video Streaming Demonstration + Roberto +

Video Streaming Demonstration

@@ -13,21 +31,12 @@
+ +
-

Send:

-
- - -
-
- - -
-

Receive:

-
-

Press a button on your controller to start

+ @@ -133,27 +142,17 @@ socket.on('data', (data) => { var vidstream = document.getElementById("stream"); var config = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] }; playStream(vidstream, null, null, null, config, function (errmsg) { console.error(errmsg); }); + + var canvas = document.getElementById("textoverlay"); + var ctx = canvas.getContext("2d"); + ctx.font = "24px Arial"; + ctx.fillStyle = "blue"; + ctx.fillText("Hello World", canvas.width/3, 30); }; - + diff --git a/raspberry/roberto/views/frontend/templates/virtualjoystick.js b/raspberry/roberto/views/frontend/templates/virtualjoystick.js new file mode 100644 index 0000000..1aa7f15 --- /dev/null +++ b/raspberry/roberto/views/frontend/templates/virtualjoystick.js @@ -0,0 +1,476 @@ +var VirtualJoystick = function(opts) +{ + opts = opts || {}; + this._container = opts.container || document.body; + this._strokeStyle = opts.strokeStyle || 'cyan'; + this._stickEl = opts.stickElement || this._buildJoystickStick(); + this._baseEl = opts.baseElement || this._buildJoystickBase(); + this._mouseSupport = opts.mouseSupport !== undefined ? opts.mouseSupport : false; + this._stationaryBase = opts.stationaryBase || false; + this._baseX = this._stickX = opts.baseX || 0; + this._baseY = this._stickY = opts.baseY || 0; + this._limitStickTravel = opts.limitStickTravel || false; + this._stickRadius = opts.stickRadius !== undefined ? opts.stickRadius : 100; + this._useCssTransform = opts.useCssTransform !== undefined ? opts.useCssTransform : false; + this._show = opts.show || false; + this._releaseX = opts.releaseX !== undefined ? opts.releaseX : null; + this._releaseY = opts.releaseY !== undefined ? opts.releaseY : null; + + this._container.style.position = "relative"; + this._container.appendChild(this._baseEl); + this._baseEl.style.position = "absolute"; + this._baseEl.style.display = "none"; + this._container.appendChild(this._stickEl); + this._stickEl.style.position = "absolute"; + this._stickEl.style.display = "none"; + + this._pressed = false; + this._touchIdx = null; + + if(this._stationaryBase === true){ + this._baseEl.style.display = ""; + this._baseEl.style.left = (this._baseX - this._baseEl.width /2)+"px"; + this._baseEl.style.top = (this._baseY - this._baseEl.height/2)+"px"; + } + + this._transform = this._useCssTransform ? this._getTransformProperty() : false; + this._has3d = this._check3D(); + + this._updatePositionOfContainer(); + window.onresize = (function (joystick) { + return function () { + return joystick._updatePositionOfContainer(); + } + })(this); + + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + this._$onTouchStart = __bind(this._onTouchStart , this); + this._$onTouchEnd = __bind(this._onTouchEnd , this); + this._$onTouchMove = __bind(this._onTouchMove , this); + this._container.addEventListener( 'touchstart' , this._$onTouchStart , false ); + this._container.addEventListener( 'touchend' , this._$onTouchEnd , false ); + this._container.addEventListener( 'touchmove' , this._$onTouchMove , false ); + if( this._mouseSupport ){ + this._$onMouseDown = __bind(this._onMouseDown , this); + this._$onMouseUp = __bind(this._onMouseUp , this); + this._$onMouseMove = __bind(this._onMouseMove , this); + this._container.addEventListener( 'mousedown' , this._$onMouseDown , false ); + this._container.addEventListener( 'mouseup' , this._$onMouseUp , false ); + this._container.addEventListener( 'mousemove' , this._$onMouseMove , false ); + } +} + +////////////////////////////////////////////////////////////////////////////////// +// // +////////////////////////////////////////////////////////////////////////////////// + +VirtualJoystick.prototype.destroy = function() +{ + this._container.removeChild(this._baseEl); + this._container.removeChild(this._stickEl); + + this._container.removeEventListener( 'touchstart' , this._$onTouchStart , false ); + this._container.removeEventListener( 'touchend' , this._$onTouchEnd , false ); + this._container.removeEventListener( 'touchmove' , this._$onTouchMove , false ); + if( this._mouseSupport ){ + this._container.removeEventListener( 'mouseup' , this._$onMouseUp , false ); + this._container.removeEventListener( 'mousedown' , this._$onMouseDown , false ); + this._container.removeEventListener( 'mousemove' , this._$onMouseMove , false ); + } +} + +/** + * @returns {Boolean} true if touchscreen is currently available, false otherwise +*/ +VirtualJoystick.touchScreenAvailable = function() +{ + return 'createTouch' in document ? true : false; +} + +/** + * microevents.js - https://github.com/jeromeetienne/microevent.js +*/ +;(function(destObj){ + destObj.addEventListener = function(event, fct){ + if(this._events === undefined) this._events = {}; + this._events[event] = this._events[event] || []; + this._events[event].push(fct); + return fct; + }; + destObj.removeEventListener = function(event, fct){ + if(this._events === undefined) this._events = {}; + if( event in this._events === false ) return; + this._events[event].splice(this._events[event].indexOf(fct), 1); + }; + destObj.dispatchEvent = function(event /* , args... */){ + if(this._events === undefined) this._events = {}; + if( this._events[event] === undefined ) return; + var tmpArray = this._events[event].slice(); + for(var i = 0; i < tmpArray.length; i++){ + var result = tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1)) + if( result !== undefined ) return result; + } + return undefined + }; +})(VirtualJoystick.prototype); + +////////////////////////////////////////////////////////////////////////////////// +// // +////////////////////////////////////////////////////////////////////////////////// + +VirtualJoystick.prototype.deltaX = function(){ return this._stickX - this._baseX; } +VirtualJoystick.prototype.deltaY = function(){ return this._stickY - this._baseY; } + +VirtualJoystick.prototype.up = function(){ + if( this._pressed === false ) return false; + var deltaX = this.deltaX(); + var deltaY = this.deltaY(); + if( deltaY >= 0 ) return false; + if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false; + return true; +} +VirtualJoystick.prototype.down = function(){ + if( this._pressed === false ) return false; + var deltaX = this.deltaX(); + var deltaY = this.deltaY(); + if( deltaY <= 0 ) return false; + if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false; + return true; +} +VirtualJoystick.prototype.right = function(){ + if( this._pressed === false ) return false; + var deltaX = this.deltaX(); + var deltaY = this.deltaY(); + if( deltaX <= 0 ) return false; + if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false; + return true; +} +VirtualJoystick.prototype.left = function(){ + if( this._pressed === false ) return false; + var deltaX = this.deltaX(); + var deltaY = this.deltaY(); + if( deltaX >= 0 ) return false; + if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false; + return true; +} + +////////////////////////////////////////////////////////////////////////////////// +// Dealing with touches/mouse movents +////////////////////////////////////////////////////////////////////////////////// + +VirtualJoystick.prototype._UpdateBase = function(){ + this._move(this._baseEl.style, (this._baseX - this._baseEl.width /2), (this._baseY - this._baseEl.height/2)); +} +VirtualJoystick.prototype._UpdateStick = function(){ + this._move(this._stickEl.style, (this._stickX - this._stickEl.width /2), (this._stickY - this._stickEl.height/2)); +} + + +VirtualJoystick.prototype._onUp = function() +{ + this._pressed = false; + this._show ? this._stickEl.style.display = "" : this._stickEl.style.display = "none"; + if(!(this._stationaryBase || this._show)) { this._baseEl.style.display = "none"; } + + if(this._releaseX !=null || this._releaseY !=null){ + if (this._releaseX !=null){ + this._stickX= this._baseX + this._releaseX; + } + if (this._releaseY !=null){ + this._stickY= this._baseY + this._releaseY; + } + } + else { + this._stickX = this._baseX; + this._stickY = this._baseY; + } + this._UpdateStick() +} + + +VirtualJoystick.prototype._onDown = function(x, y) +{ + this._pressed = true; + if(this._stationaryBase == false){ + this._baseX = x; + this._baseY = y; + this._baseEl.style.display = ""; + this._UpdateBase() + } + + this._stickX = x; + this._stickY = y; + + if(this._limitStickTravel === true){ + var deltaX = this.deltaX(); + var deltaY = this.deltaY(); + var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) ); + if(stickDistance > this._stickRadius){ + var stickNormalizedX = deltaX / stickDistance; + var stickNormalizedY = deltaY / stickDistance; + + this._stickX = stickNormalizedX * this._stickRadius + this._baseX; + this._stickY = stickNormalizedY * this._stickRadius + this._baseY; + } + } + + this._stickEl.style.display = ""; + this._UpdateStick() +} + +VirtualJoystick.prototype._onMove = function(x, y) +{ + if( this._pressed === true ){ + this._stickX = x; + this._stickY = y; + + if(this._limitStickTravel === true){ + var deltaX = this.deltaX(); + var deltaY = this.deltaY(); + var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) ); + if(stickDistance > this._stickRadius){ + var stickNormalizedX = deltaX / stickDistance; + var stickNormalizedY = deltaY / stickDistance; + + this._stickX = stickNormalizedX * this._stickRadius + this._baseX; + this._stickY = stickNormalizedY * this._stickRadius + this._baseY; + } + } + this._UpdateStick() + } +} + + +////////////////////////////////////////////////////////////////////////////////// +// bind touch events (and mouse events for debug) // +// mouse events +////////////////////////////////////////////////////////////////////////////////// +// container offset. +VirtualJoystick.prototype._updatePositionOfContainer = function () { + var x = 0, y = 0; + var element = this._container; + if(this._stationaryBase === true){ + this._UpdateBase() + while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { + x += element.offsetLeft; + y += element.offsetTop; + element = element.offsetParent; + } + } + else { + while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { + x += element.offsetLeft - element.scrollLeft; + y += element.offsetTop - element.scrollTop; + element = element.offsetParent; + } + } + this._containerX = x; + this._containerY = y; +} + +VirtualJoystick.prototype._onMouseUp = function(event) +{ + return this._onUp(); +} + +VirtualJoystick.prototype._onMouseDown = function(event) +{ + // notify event for validation + var isValid = this.dispatchEvent('mouseStartValidation', event); + if( isValid === false ) return; + + event.preventDefault(); + var x = event.clientX - this._containerX; + var y = event.clientY - this._containerY; + return this._onDown(x, y); +} + +VirtualJoystick.prototype._onMouseMove = function(event) +{ + var x = event.clientX - this._containerX; + var y = event.clientY - this._containerY; + return this._onMove(x, y); +} + +////////////////////////////////////////////////////////////////////////////////// +// touch events // +////////////////////////////////////////////////////////////////////////////////// + +VirtualJoystick.prototype._onTouchStart = function(event) +{ + // if there is already a touch inprogress do nothing + if( this._touchIdx !== null ) return; + + // notify event for validation + var isValid = this.dispatchEvent('touchStartValidation', event); + if( isValid === false ) return; + + // dispatch touchStart + this.dispatchEvent('touchStart', event); + + event.preventDefault(); + // get the first who changed + var touch = event.changedTouches[0]; + // set the touchIdx of this joystick + this._touchIdx = touch.identifier; + + // forward the action + var x = touch.pageX - this._containerX; + var y = touch.pageY - this._containerY; + return this._onDown(x, y) +} + +VirtualJoystick.prototype._onTouchEnd = function(event) +{ + // if there is no touch in progress, do nothing + if( this._touchIdx === null ) return; + + // dispatch touchEnd + this.dispatchEvent('touchEnd', event); + + // try to find our touch event + var touchList = event.changedTouches; + for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++); + // if touch event isnt found, + if( i === touchList.length) return; + + // reset touchIdx - mark it as no-touch-in-progress + this._touchIdx = null; + +//?????? +// no preventDefault to get click event on ios +event.preventDefault(); + + return this._onUp() +} + +VirtualJoystick.prototype._onTouchMove = function(event) +{ + // if there is no touch in progress, do nothing + if( this._touchIdx === null ) return; + + // try to find our touch event + var touchList = event.changedTouches; + for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++ ); + // if touch event with the proper identifier isnt found, do nothing + if( i === touchList.length) return; + var touch = touchList[i]; + + event.preventDefault(); + + var x = touch.pageX - this._containerX; + var y = touch.pageY - this._containerY; + return this._onMove(x, y) +} + + +////////////////////////////////////////////////////////////////////////////////// +// build default stickEl and baseEl // +////////////////////////////////////////////////////////////////////////////////// + +/** + * build the canvas for joystick base + */ +VirtualJoystick.prototype._buildJoystickBase = function() +{ + var canvas = document.createElement( 'canvas' ); + canvas.width = 126; + canvas.height = 126; + + var ctx = canvas.getContext('2d'); + ctx.beginPath(); + ctx.strokeStyle = this._strokeStyle; + ctx.lineWidth = 6; + ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true); + ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle = this._strokeStyle; + ctx.lineWidth = 2; + ctx.arc( canvas.width/2, canvas.width/2, 60, 0, Math.PI*2, true); + ctx.stroke(); + + return canvas; +} + +/** + * build the canvas for joystick stick + */ +VirtualJoystick.prototype._buildJoystickStick = function() +{ + var canvas = document.createElement( 'canvas' ); + canvas.width = 86; + canvas.height = 86; + var ctx = canvas.getContext('2d'); + ctx.beginPath(); + ctx.strokeStyle = this._strokeStyle; + ctx.lineWidth = 6; + ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true); + ctx.stroke(); + return canvas; +} + +////////////////////////////////////////////////////////////////////////////////// +// move using translate3d method with fallback to translate > 'top' and 'left' +// modified from https://github.com/component/translate and dependents +////////////////////////////////////////////////////////////////////////////////// + +VirtualJoystick.prototype._move = function(style, x, y) // moves the top left of cavas to (x,y). +{ + if (this._transform) { + if (this._has3d) { + style[this._transform] = 'translate3d(' + x + 'px,' + y + 'px, 0)'; + } else { + style[this._transform] = 'translate(' + x + 'px,' + y + 'px)'; + } + } else { + style.left = x + 'px'; + style.top = y + 'px'; + } +} + +VirtualJoystick.prototype._getTransformProperty = function() +{ + var styles = [ + 'webkitTransform', + 'MozTransform', + 'msTransform', + 'OTransform', + 'transform' + ]; + + var el = document.createElement('p'); + var style; + + for (var i = 0; i < styles.length; i++) { + style = styles[i]; + if (null != el.style[style]) { + return style; + } + } +} + +VirtualJoystick.prototype._check3D = function() +{ + var prop = this._getTransformProperty(); + // IE8<= doesn't have `getComputedStyle` + if (!prop || !window.getComputedStyle) return module.exports = false; + + var map = { + webkitTransform: '-webkit-transform', + OTransform: '-o-transform', + msTransform: '-ms-transform', + MozTransform: '-moz-transform', + transform: 'transform' + }; + + // from: https://gist.github.com/lorenzopolidori/3794226 + var el = document.createElement('div'); + el.style[prop] = 'translate3d(1px,1px,1px)'; + document.body.insertBefore(el, null); + var val = getComputedStyle(el).getPropertyValue(map[prop]); + document.body.removeChild(el); + var exports = null != val && val.length && 'none' != val; + return exports; +} +