/* Buttons utility * * Normalize input to a simple 5 way nav plus back button * */ (function (exports) { "use strict"; /** * Buttons provides a simple abstraction for a 5-way nav plus back and media buttons. * Buttons trigger events: 'buttonpress', optionally 'buttonrepeat' at a decaying interval, and 'buttonrelease' * Only one button at a time can be pressed, other key events are ignored until that button is released. * Button codes are based on keyboard keyCodes for simplicity. * @return {*} * @constructor */ var Buttons = function () { // make it a singleton if (exports.buttons) { return buttons; } /** * Buttons uses Events to trigger the button events */ Events.call(this, ['buttonpress', 'buttonrepeat', 'buttonrelease']); /** * constants for buttons, based onf keyCode for key events: */ this.UP = 38; this.DOWN = 40; this.LEFT = 37; this.RIGHT = 39; this.SELECT = 13; this.BACK = 8; this.REWIND = 227; this.PLAY_PAUSE = 179; this.FAST_FORWARD = 228; this.KEYCODES = [8, 13, 37, 38, 39, 40, 179, 227, 228]; this.BUTTON_INTERVALS = [250, 350, 500]; // records if the button is currently held down for different interaction on button hold this.startHeldDown = {}; this.intervalIndex = 0; this.currentKey = 0; this.scrollTimerId = 0; this.suspended = false; /** * reset abandons any subsequent events from the current button press, no more buttonrepeat or buttonrelease events * will be generated even if the button remains held down */ this.reset = function () { this.currentKey = 0; if (this.scrollTimerId) { window.clearTimeout(this.scrollTimerId); this.scrollTimerId = 0; } this.suspended = false; }; /** * resync with browser key state, needed when window loses focus and system keyUp events may get lost * if a button is held down the next keyDown event (from auto-repeat) will trigger a 'buttonpress' */ this.resync = function () { this.reset(); this.startHeldDown = {}; }; /** * suspend processing of key events and let them propagate normally, e.g. so that an input * element can get text, call reset() to end suspension */ this.suspend = function () { this.resync(); this.suspended = true; }; // gets the next delay interval, pass reset=true to start over this.getButtonInterval = function (reset) { if (reset) { this.intervalIndex = this.BUTTON_INTERVALS.length; } if (this.intervalIndex > 0) { this.intervalIndex--; } return this.BUTTON_INTERVALS[this.intervalIndex]; }; // browser integration, de-bounce and de-duplicate key events, only allow one button at a time to be handled this.handleKeyDown = function (e) { var keyCode = e.keyCode; if (!this.suspended && this.KEYCODES.indexOf(keyCode) >= 0) { e.preventDefault(); if (!this.startHeldDown[keyCode]) { // ignore any repeated keyDown events (cleared on keyUp) this.startHeldDown[keyCode] = e.timeStamp; if (!this.currentKey) { this.currentKey = keyCode; this.scrollTimerId = window.setTimeout(this.doRepeat, this.getButtonInterval(true)); this.trigger('buttonpress', { type: 'buttonpress', keyCode: keyCode }); } } } }.bind(this); this.handleKeyUp = function (e) { var keyCode = e.keyCode; if (!this.suspended && this.KEYCODES.indexOf(keyCode) >= 0) { e.preventDefault(); if (this.startHeldDown[keyCode]) { // ignore any keyUp events that aren't for keys which are down this.startHeldDown[keyCode] = 0; // only emit release if this handler also got press if (this.currentKey === keyCode) { this.currentKey = 0; if (this.scrollTimerId) { window.clearTimeout(this.scrollTimerId); this.scrollTimerId = 0; } this.trigger('buttonrelease', { type: 'buttonrelease', keyCode: keyCode }); } } } }.bind(this); this.doRepeat = function () { if (!this.suspended && this.currentKey && this.startHeldDown[this.currentKey]) { this.scrollTimerId = window.setTimeout(this.doRepeat, this.getButtonInterval()); this.trigger('buttonrepeat', { type: 'buttonrepeat', keyCode: this.currentKey }); } }.bind(this); window.addEventListener("keydown", this.handleKeyDown, false); window.addEventListener("keyup", this.handleKeyUp, false); // the system handles the 'back' button on the remote and turns it into a browser back event, so // we use the history API to watch for 'popstate' (back button press) events and convert these into // button press events for the BACKSPACE key. // this is documented more in the AWV app knowledge base window.addEventListener("load", function () { window.addEventListener("popstate", function (evt) { if (window.history.state !== "backhandler") { // state will be AFTER the 'backhandler' is popped, so we ignore it if it's popping dummy state // handle same as BACKSPACE keycode if (!this.suspended) { this.handleKeyDown({ type: 'keydown', keyCode: this.BACK, timeStamp: evt.timeStamp, preventDefault: function () { } }); this.handleKeyUp({ type: 'keyup', keyCode: this.BACK, timeStamp: evt.timeStamp, preventDefault: function () { } }); } window.history.pushState("backhandler", null, null); } }.bind(this)); if (window.history.state !== "backhandler") { window.history.pushState("backhandler", null, null); // pushing a dummy state so that popstate is always called } }.bind(this)); }; exports.Buttons = Buttons; exports.buttons = new Buttons(); }(window));