/* Playground 1.0.2 http://canvasquery.org (c) 2012-2014 http://rezoner.net Playground may be freely distributed under the MIT license. */ function playground(args) { return new Playground(args); }; /* utitlities */ playground.extend = function() { for (var i = 1; i < arguments.length; i++) { for (var j in arguments[i]) { arguments[0][j] = arguments[i][j]; } } return arguments[0]; }; playground.throttle = function(fn, threshold) { threshold || (threshold = 250); var last, deferTimer; return function() { var context = this; var now = +new Date, args = arguments; if (last && now < last + threshold) { // hold on to it clearTimeout(deferTimer); deferTimer = setTimeout(function() { last = now; fn.apply(context, args); }, threshold); } else { last = now; fn.apply(context, args); } }; }; /* constructor */ function Playground(args) { /* defaults */ playground.extend(this, { smoothing: 1, scale: 1, preventKeyboardDefault: true, preventContextMenu: true }, args); if (!this.width || !this.height) this.fitToContainer = true; if (!this.container) this.container = document.body; if (this.container !== document.body) this.customContainer = true; if (typeof this.container === "string") this.container = document.querySelector(this.container); /* state */ this.state = {}; /* layer */ if (!args.layer) { cq.smoothing = this.smoothing; if (window.CocoonJS) { this.layer = cq.cocoon(1, 1); this.layer.appendTo(this.container); this.screen = this.layer; } else { this.layer = cq(1, 1); if (this.scaleToFit) { this.screen = cq(1, 1); this.screen.appendTo(this.container); } else { this.layer.appendTo(this.container); this.screen = this.layer; } } } var canvas = this.screen.canvas; /* events */ this.eventsHandler = this.eventsHandler.bind(this); /* mouse */ this.mouse = new playground.Mouse(canvas); this.mouse.on("event", this.eventsHandler); this.mouse.preventContextMenu = this.preventContextMenu; /* touch */ this.touch = new playground.Touch(canvas); this.touch.on("event", this.eventsHandler); /* keyboard */ this.keyboard = new playground.Keyboard(); this.keyboard.preventDefault = this.preventKeyboardDefault; this.keyboard.on("event", this.eventsHandler); /* gamepads */ this.gamepads = new playground.Gamepads(); this.gamepads.on("event", this.eventsHandler); /* window resize */ window.addEventListener("resize", this.resizeHandler.bind(this)); setTimeout(this.resizeHandler.bind(this), 1); /* video recorder */ this.videoRecorder = new playground.VideoRecorder(this); /* game loop */ var self = this; var lastTick = Date.now(); function step() { requestAnimationFrame(step); var delta = Date.now() - lastTick; lastTick = Date.now(); if (delta > 1000) return; var dt = delta / 1000; if (self.loader.count <= 0) { if (self.step) self.step(dt); if (self.state.step) self.state.step(dt); if (self.render) self.render(dt); if (self.state.render) self.state.render(dt); if (self.postrender) self.postrender(dt); if (self.state.postrender) self.state.postrender(dt); } else { self.renderLoader(dt); } if (self.scaleToFit) { self.screen.save(); self.screen.translate(self.offsetX, self.offsetY); self.screen.scale(self.scale, self.scale); // self.layer.drawImage(self.scanlines.canvas, 0, 0); self.screen.drawImage(self.layer.canvas, 0, 0); self.screen.restore(); } self.gamepads.step(dt); self.videoRecorder.step(dt); }; requestAnimationFrame(step); /* assets */ /* default audio format */ var canPlayMp3 = (new Audio).canPlayType("audio/mp3"); var canPlayOgg = (new Audio).canPlayType('audio/ogg; codecs="vorbis"'); if (canPlayMp3) this.audioFormat = "mp3"; else this.audioFormat = "ogg"; this.loader = new playground.Loader(); this.images = {}; this.sounds = {}; this.loadFoo(0.5); if (this.create) setTimeout(this.create.bind(this)); this.loader.on("ready", function() { if (self.ready) self.ready(); self.ready = function() {}; }); }; Playground.prototype = { setState: function(state) { state.app = this; if (this.state && this.state.leave) this.state.leave(); this.state = state; if (this.state && this.state.enter) this.state.enter(); }, eventsHandler: function(event, data) { if (this[event]) this[event](data); if (this.state[event]) this.state[event](data); }, resizeHandler: function() { if (this.customContainer) { var containerWidth = this.container.offsetWidth; var containerHeight = this.container.offsetHeight; } else { var containerWidth = window.innerWidth; var containerHeight = window.innerHeight; } if (this.fitToContainer) { this.width = this.containerWidth; this.height = this.containerHeight; } if (!this.scaleToFit) { if (this.fitToContainer) { this.width = containerWidth; this.height = containerHeight; } this.offsetX = 0; this.offsetY = 0; } else { this.screen.width = containerWidth; this.screen.height = containerHeight; this.scale = Math.min(containerWidth / this.width, containerHeight / this.height); if (this.roundScale) this.scale = Math.max(1, Math.floor(this.scale)); this.offsetX = containerWidth / 2 - this.scale * (this.width / 2) | 0; this.offsetY = containerHeight / 2 - this.scale * (this.height / 2) | 0; this.mouse.scale = this.scale; this.mouse.offsetX = this.offsetX; this.mouse.offsetY = this.offsetY; this.touch.scale = this.scale; this.touch.offsetX = this.offsetX; this.touch.offsetY = this.offsetY; } this.layer.width = this.width; this.layer.height = this.height; this.center = { x: this.width / 2 | 0, y: this.height / 2 | 0 }; this.screen.clear("#000"); this.eventsHandler("resize"); this.mouse.update(); this.touch.update(); }, renderLoader: function() { var height = this.height / 10 | 0; var x = 32; var width = this.width - x * 2; var y = this.height / 2 - height / 2 | 0; this.layer.clear("#000"); this.layer.strokeStyle("#fff").lineWidth(2).strokeRect(x, y, width, height); this.layer.fillStyle("#fff").fillRect(x, y, width * this.loader.progress | 0, height); }, record: function(args) { this.videoRecorder.toggle(args); }, /* imaginary timeout to delay loading */ loadFoo: function(timeout) { if (!this.foofooLoader) this.foofooLoader = 0; var loader = this.loader; this.loader.add("foo " + timeout); setTimeout(function() { loader.ready("foo " + timeout); }, (this.foofooLoader += timeout * 1000)); }, /* images */ loadImages: function() { for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; /* polymorphism at its finest */ if (typeof arg === "object") { for (var key in arg) this.addImages(arg[key]); } else { /* if argument is not an object/array let's try to load it */ var filename = arg; var loader = this.loader; var fileinfo = filename.match(/(.*)\..*/); var key = fileinfo ? fileinfo[1] : filename; /* filename defaults to png */ if (!fileinfo) filename += ".png"; var path = "images/" + filename; this.loader.add(path); var image = this.images[key] = new Image; image.addEventListener("load", function() { loader.ready(path); }); image.addEventListener("error", function() { loader.error(path); }); image.src = path; } } }, /* sounds */ loadSounds: function() { for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; /* polymorphism at its finest */ if (typeof arg === "object") { for (var key in arg) this.loadSounds(arg[key]); } else { /* if argument is not an object/array let's try to load it */ var filename = arg; var loader = this.loader; var key = filename; filename += "." + this.audioFormat; var path = "sounds/" + filename; this.loader.add(path); var audio = this.sounds[key] = new Audio; audio.addEventListener("canplay", function() { loader.ready(path); }); audio.addEventListener("error", function() { loader.error(path); }); audio.src = path; } } }, loadFont: function(name) { var styleNode = document.createElement("style"); styleNode.type = "text/css"; var formats = { "woff": "woff", "ttf": "truetype" }; var sources = ""; for (var ext in formats) { var type = formats[ext]; sources += " url(\"fonts/" + name + "." + ext + "\") format('" + type + "');" } styleNode.textContent = "@font-face { font-family: '" + name + "'; src: " + sources + " }"; document.head.appendChild(styleNode); var layer = cq(32, 32); layer.font("10px huj"); layer.fillText(16, 16, 16).trim(); var width = layer.width; var height = layer.height; this.loader.add("font " + name); var self = this; function check() { var layer = cq(32, 32); layer.font("10px " + name).fillText(16, 16, 16); layer.trim(); if (layer.width !== width || layer.height !== height) { self.loader.ready("font " + name); } else { setTimeout(check, 250); } }; check(); }, playSound: function(key, loop) { var sound = this.sounds[key]; sound.currentTime = 0; sound.loop = loop; sound.play(); return sound; }, stopSound: function(sound) { if (typeof sound === "string") sound = this.sounds[sound]; sound.pause(); } }; playground.Events = function() { this.listeners = {}; }; playground.Events.prototype = { on: function(event, callback) { if (!this.listeners[event]) this.listeners[event] = []; this.listeners[event].push(callback); return callback; }, once: function(event, callback) { callback.once = true; if (!this.listeners[event]) this.listeners[event] = []; this.listeners[event].push(callback); return callback; }, off: function(event, callback) { for (var i = 0, len = this.listeners[event].length; i < len; i++) { if (this.listeners[event][i]._remove) { this.listeners[event].splice(i--, 1); len--; } } }, trigger: function(event, data) { /* if you prefer events pipe */ if (this.listeners["event"]) { for (var i = 0, len = this.listeners["event"].length; i < len; i++) { this.listeners["event"][i](event, data); } } /* or subscribed to single event */ if (this.listeners[event]) { for (var i = 0, len = this.listeners[event].length; i < len; i++) { var listener = this.listeners[event][i]; listener(data); if (listener.once) { this.listeners[event].splice(i--, 1); } } } } }; /* Mouse */ playground.Mouse = function(element) { var self = this; playground.Events.call(this); this.element = element; this.buttons = {}; this.mousemoveEvent = {}; this.mousedownEvent = {}; this.mouseupEvent = {}; this.mousewheelEvent = {}; this.x = 0; this.y = 0; this.offsetX = 0; this.offsetY = 0; this.scale = 1; element.addEventListener("mousemove", this.mousemove.bind(this)); element.addEventListener("mousedown", this.mousedown.bind(this)); element.addEventListener("mouseup", this.mouseup.bind(this)); this.enableMousewheel(); this.element.addEventListener("contextmenu", function(e) { if (self.preventContextMenu) e.preventDefault(); }); }; playground.Mouse.prototype = { getElementOffset: function(element) { var offsetX = 0; var offsetY = 0; do { offsetX += element.offsetLeft; offsetY += element.offsetTop; } while ((element = element.offsetParent)); return { x: offsetX, y: offsetY }; }, update: function() { this.elementOffset = this.getElementOffset(this.element); }, mousemove: function(e) { this.x = this.mousemoveEvent.x = (e.pageX - this.elementOffset.x - this.offsetX) / this.scale | 0; this.y = this.mousemoveEvent.y = (e.pageY - this.elementOffset.y - this.offsetY) / this.scale | 0; this.mousemoveEvent.original = e; this.trigger("mousemove", this.mousemoveEvent); }, mousedown: function(e) { var buttonName = ["left", "middle", "right"][e.button]; this.mousedownEvent.x = this.mousemoveEvent.x; this.mousedownEvent.y = this.mousemoveEvent.y; this.mousedownEvent.button = buttonName; this.mousedownEvent.original = e; this[buttonName] = true; this.trigger("mousedown", this.mousedownEvent); }, mouseup: function(e) { var buttonName = ["left", "middle", "right"][e.button]; this.mouseupEvent.x = this.mousemoveEvent.x; this.mouseupEvent.y = this.mousemoveEvent.y; this.mouseupEvent.button = buttonName; this.mouseupEvent.original = e; this[buttonName] = false; this.trigger("mouseup", this.mouseupEvent); }, mousewheel: function(e) { this.mousewheelEvent.x = this.mousemoveEvent.x; this.mousewheelEvent.y = this.mousemoveEvent.y; this.mousewheelEvent.button = ["none", "left", "middle", "right"][e.button]; this.mousewheelEvent.original = e; this[e.button] = false; this.trigger("mousewheel", this.mousewheelEvent); }, enableMousewheel: function() { var eventNames = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll']; var callback = this.mousewheel.bind(this); var self = this; for (var i = eventNames.length; i;) { self.element.addEventListener(eventNames[--i], playground.throttle(function(event) { var orgEvent = event || window.event, args = [].slice.call(arguments, 1), delta = 0, deltaX = 0, deltaY = 0, absDelta = 0, absDeltaXY = 0, fn; event.type = "mousewheel"; // Old school scrollwheel delta if (orgEvent.wheelDelta) { delta = orgEvent.wheelDelta; } if (orgEvent.detail) { delta = orgEvent.detail * -1; } // New school wheel delta (wheel event) if (orgEvent.deltaY) { deltaY = orgEvent.deltaY * -1; delta = deltaY; } // Webkit if (orgEvent.wheelDeltaY !== undefined) { deltaY = orgEvent.wheelDeltaY; } var result = delta ? delta : deltaY; self.mousewheelEvent.x = self.mousemoveEvent.x; self.mousewheelEvent.y = self.mousemoveEvent.y; self.mousewheelEvent.delta = result / Math.abs(result); self.mousewheelEvent.original = orgEvent; callback(self.mousewheelEvent); event.preventDefault(); }, 40), false); } } }; playground.extend(playground.Mouse.prototype, playground.Events.prototype); /* Touch */ playground.Touch = function(element) { playground.Events.call(this); this.element = element; this.buttons = {}; this.touchmoveEvent = {}; this.touchstartEvent = {}; this.touchendEvent = {}; this.x = 0; this.y = 0; this.offsetX = 0; this.offsetY = 0; this.scale = 1; element.addEventListener("touchmove", this.touchmove.bind(this)); element.addEventListener("touchstart", this.touchstart.bind(this)); element.addEventListener("touchend", this.touchend.bind(this)); }; playground.Touch.prototype = { getElementOffset: function(element) { var offsetX = 0; var offsetY = 0; do { offsetX += element.offsetLeft; offsetY += element.offsetTop; } while ((element = element.offsetParent)); return { x: offsetX, y: offsetY }; }, update: function() { this.elementOffset = this.getElementOffset(this.element); }, touchmove: function(e) { var touch = e.touches[0] || e.changedTouches[0]; this.x = this.touchmoveEvent.x = (touch.pageX - this.elementOffset.x - this.offsetX) / this.scale | 0; this.y = this.touchmoveEvent.y = (touch.pageY - this.elementOffset.y - this.offsetY) / this.scale | 0; this.touchmoveEvent.original = e; this.touchmoveEvent.identifier = e.identifier; this.trigger("touchmove", this.touchmoveEvent); e.preventDefault(); }, touchstart: function(e) { this.touchstartEvent.x = this.touchmoveEvent.x; this.touchstartEvent.y = this.touchmoveEvent.y; this.touchstartEvent.original = e; this.touchstartEvent.identifier = e.identifier; this.pressed = true; this.trigger("touchstart", this.touchstartEvent); }, touchend: function(e) { this.touchendEvent.x = this.touchmoveEvent.x; this.touchendEvent.y = this.touchmoveEvent.y; this.touchendEvent.original = e; this.touchendEvent.identifier = e.identifier; this.pressed = false; this.trigger("touchend", this.touchendEvent); } }; playground.extend(playground.Touch.prototype, playground.Events.prototype); /* Keyboard */ playground.Keyboard = function() { playground.Events.call(this); this.keys = {}; document.addEventListener("keydown", this.keydown.bind(this)); document.addEventListener("keyup", this.keyup.bind(this)); document.addEventListener("keypress", this.keypress.bind(this)); this.keydownEvent = {}; this.keyupEvent = {}; }; playground.Keyboard.prototype = { keycodes: { 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "delete", 8: "backspace", 9: "tab", 13: "enter", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20: "capslock", 27: "escape", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scrolllock", 186: "semicolon", 187: "equal", 188: "comma", 189: "dash", 190: "period", 191: "slash", 192: "graveaccent", 219: "openbracket", 220: "backslash", 221: "closebraket", 222: "singlequote" }, keypress: function(e) { }, keydown: function(e) { if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); else var keyName = this.keycodes[e.which]; if (this.keys[keyName]) return; this.keydownEvent.key = keyName; this.keydownEvent.original = e; this.keys[keyName] = true; this.trigger("keydown", this.keydownEvent); if (this.preventDefault && document.activeElement === document.body) { e.returnValue = false; e.keyCode = 0; e.preventDefault(); e.stopPropagation(); } }, keyup: function(e) { if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); else var keyName = this.keycodes[e.which]; this.keyupEvent.key = keyName; this.keyupEvent.original = e; this.keys[keyName] = false; this.trigger("keyup", this.keyupEvent); } }; playground.extend(playground.Keyboard.prototype, playground.Events.prototype); /* Gamepad */ playground.Gamepads = function() { playground.Events.call(this); this.getGamepads = navigator.getGamepads || navigator.webkitGetGamepads; this.gamepadmoveEvent = {}; this.gamepaddownEvent = {}; this.gamepadupEvent = {}; this.gamepads = {}; }; playground.Gamepads.prototype = { buttons: { 0: "1", 1: "2", 2: "3", 3: "4", 4: "l1", 5: "r1", 6: "l2", 7: "r2", 8: "select", 9: "start", 12: "up", 13: "down", 14: "left", 15: "right" }, zeroState: function() { var buttons = []; for (var i = 0; i <= 15; i++) { buttons.push({ pressed: false, value: 0 }); } return { axes: [], buttons: buttons }; }, createGamepad: function() { var result = { buttons: {}, sticks: [{ x: 0, y: 0 }, { x: 0, y: 0 }] }; for (var i = 0; i < 16; i++) { var key = this.buttons[i]; result.buttons[key] = false; } return result; }, step: function() { if (!navigator.getGamepads) return; var gamepads = navigator.getGamepads(); for (var i = 0; i < gamepads.length; i++) { var current = gamepads[i]; if (!current) continue; if (!this[i]) this[i] = this.createGamepad(); /* have to concat the current.buttons because the are read-only */ var buttons = [].concat(current.buttons); /* hack for missing dpads */ for (var h = 12; h <= 15; h++) { if (!buttons[h]) buttons[h] = { pressed: false, value: 0 }; } var previous = this[i]; /* axes (sticks) to buttons */ if (current.axes) { if (current.axes[0] < 0) buttons[14].pressed = true; if (current.axes[0] > 0) buttons[15].pressed = true; if (current.axes[1] < 0) buttons[12].pressed = true; if (current.axes[1] > 0) buttons[13].pressed = true; previous.sticks[0].x = current.axes[0].value; previous.sticks[0].y = current.axes[1].value; previous.sticks[1].x = current.axes[2].value; previous.sticks[1].y = current.axes[3].value; } /* check buttons changes */ for (var j = 0; j < buttons.length; j++) { var key = this.buttons[j]; /* gamepad down */ if (buttons[j].pressed && !previous.buttons[key]) { previous.buttons[key] = true; this.gamepaddownEvent.button = this.buttons[j]; this.gamepaddownEvent.gamepad = i; this.trigger("gamepaddown", this.gamepaddownEvent); } /* gamepad up */ else if (!buttons[j].pressed && previous.buttons[key]) { previous.buttons[key] = false; this.gamepadupEvent.button = this.buttons[j]; this.gamepadupEvent.gamepad = i; this.trigger("gamepadup", this.gamepadupEvent); } } } } }; playground.extend(playground.Gamepads.prototype, playground.Events.prototype); /* Loader */ playground.Loader = function() { playground.Events.call(this); this.reset(); }; playground.Loader.prototype = { /* loader */ add: function(id) { this.queue++; this.count++; this.trigger("add", id); }, error: function(id) { console.log("unable to load " + id); this.trigger("error", id); }, ready: function(id) { this.queue--; this.progress = 1 - this.queue / this.count; this.trigger("load", id); if (this.queue <= 0) { this.trigger("ready"); this.reset(); } }, reset: function() { this.progress = 0; this.queue = 0; this.count = 0; } }; playground.extend(playground.Loader.prototype, playground.Events.prototype); CanvasQuery.Layer.prototype.playground = function(args) { args.layer = this; return playground(args); }; /* Video recorder */ /* whammy - https://github.com/antimatter15/whammy */ window.Whammy = function() { function h(a, b) { for (var c = r(a), c = [{ id: 440786851, data: [{ data: 1, id: 17030 }, { data: 1, id: 17143 }, { data: 4, id: 17138 }, { data: 8, id: 17139 }, { data: "webm", id: 17026 }, { data: 2, id: 17031 }, { data: 2, id: 17029 }] }, { id: 408125543, data: [{ id: 357149030, data: [{ data: 1E6, id: 2807729 }, { data: "whammy", id: 19840 }, { data: "whammy", id: 22337 }, { data: s(c.duration), id: 17545 }] }, { id: 374648427, data: [{ id: 174, data: [{ data: 1, id: 215 }, { data: 1, id: 25541 }, { data: 0, id: 156 }, { data: "und", id: 2274716 }, { data: "V_VP8", id: 134 }, { data: "VP8", id: 2459272 }, { data: 1, id: 131 }, { id: 224, data: [{ data: c.width, id: 176 }, { data: c.height, id: 186 }] }] }] }] }], e = 0, d = 0; e < a.length;) { var g = [], f = 0; do g.push(a[e]), f += a[e].duration, e++; while (e < a.length && 3E4 > f); var h = 0, g = { id: 524531317, data: [{ data: d, id: 231 }].concat(g.map(function(a) { var b = t({ discardable: 0, frame: a.data.slice(4), invisible: 0, keyframe: 1, lacing: 0, trackNum: 1, timecode: Math.round(h) }); h += a.duration; return { data: b, id: 163 } })) }; c[1].data.push(g); d += f } return m(c, b) } function r(a) { for (var b = a[0].width, c = a[0].height, e = a[0].duration, d = 1; d < a.length; d++) { if (a[d].width != b) throw "Frame " + (d + 1) + " has a different width"; if (a[d].height != c) throw "Frame " + (d + 1) + " has a different height"; if (0 > a[d].duration || 32767 < a[d].duration) throw "Frame " + (d + 1) + " has a weird duration (must be between 0 and 32767)"; e += a[d].duration } return { duration: e, width: b, height: c } } function u(a) { for (var b = []; 0 < a;) b.push(a & 255), a >>= 8; return new Uint8Array(b.reverse()) } function n(a) { var b = []; a = (a.length % 8 ? Array(9 - a.length % 8).join("0") : "") + a; for (var c = 0; c < a.length; c += 8) b.push(parseInt(a.substr(c, 8), 2)); return new Uint8Array(b) } function m(a, b) { for (var c = [], e = 0; e < a.length; e++) { var d = a[e].data; "object" == typeof d && (d = m(d, b)); "number" == typeof d && (d = n(d.toString(2))); if ("string" == typeof d) { for (var g = new Uint8Array(d.length), f = 0; f < d.length; f++) g[f] = d.charCodeAt(f); d = g } f = d.size || d.byteLength || d.length; g = Math.ceil(Math.ceil(Math.log(f) / Math.log(2)) / 8); f = f.toString(2); f = Array(7 * g + 8 - f.length).join("0") + f; g = Array(g).join("0") + "1" + f; c.push(u(a[e].id)); c.push(n(g)); c.push(d) } return b ? (c = p(c), new Uint8Array(c)) : new Blob(c, { type: "video/webm" }) } function p(a, b) { null == b && (b = []); for (var c = 0; c < a.length; c++) "object" == typeof a[c] ? p(a[c], b) : b.push(a[c]); return b } function t(a) { var b = 0; a.keyframe && (b |= 128); a.invisible && (b |= 8); a.lacing && (b |= a.lacing << 1); a.discardable && (b |= 1); if (127 < a.trackNum) throw "TrackNumber > 127 not supported"; return [a.trackNum | 128, a.timecode >> 8, a.timecode & 255, b].map(function(a) { return String.fromCharCode(a) }).join("") + a.frame } function q(a) { for (var b = a.RIFF[0].WEBP[0], c = b.indexOf("\u009d\u0001*"), e = 0, d = []; 4 > e; e++) d[e] = b.charCodeAt(c + 3 + e); e = d[1] << 8 | d[0]; c = e & 16383; e = d[3] << 8 | d[2]; return { width: c, height: e & 16383, data: b, riff: a } } function k(a) { for (var b = 0, c = {}; b < a.length;) { var e = a.substr(b, 4), d = parseInt(a.substr(b + 4, 4).split("").map(function(a) { a = a.charCodeAt(0).toString(2); return Array(8 - a.length + 1).join("0") + a }).join(""), 2), g = a.substr(b + 4 + 4, d), b = b + (8 + d); c[e] = c[e] || []; "RIFF" == e || "LIST" == e ? c[e].push(k(g)) : c[e].push(g) } return c } function s(a) { return [].slice.call(new Uint8Array((new Float64Array([a])).buffer), 0).map(function(a) { return String.fromCharCode(a) }).reverse().join("") } function l(a, b) { this.frames = []; this.duration = 1E3 / a; this.quality = b || .8 } l.prototype.add = function(a, b) { if ("undefined" != typeof b && this.duration) throw "you can't pass a duration if the fps is set"; if ("undefined" == typeof b && !this.duration) throw "if you don't have the fps set, you ned to have durations here."; "canvas" in a && (a = a.canvas); if ("toDataURL" in a) a = a.toDataURL("image/webp", this.quality); else if ("string" != typeof a) throw "frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string"; if (!/^data:image\/webp;base64,/ig.test(a)) throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp"; this.frames.push({ image: a, duration: b || this.duration }) }; l.prototype.compile = function(a) { return new h(this.frames.map(function(a) { var c = q(k(atob(a.image.slice(23)))); c.duration = a.duration; return c }), a) }; return { Video: l, fromImageArray: function(a, b, c) { return h(a.map(function(a) { a = q(k(atob(a.slice(23)))); a.duration = 1E3 / b; return a }), c) }, toWebM: h } }(); playground.VideoRecorder = function(app, args) { this.app = app; }; playground.VideoRecorder.prototype = { setup: function(args) { this.region = false; playground.extend(this, { followMouse: false, framerate: 20, scale: 1 }, args); if (!this.region) { this.region = [0, 0, this.app.layer.width, this.app.layer.height]; } this.playbackRate = this.framerate / 60; this.layer = cq(this.region[2] * this.scale | 0, this.region[3] * this.scale | 0); }, start: function(args) { this.setup(args); this.encoder = new Whammy.Video(this.framerate); this.captureTimeout = 0; this.recording = true; }, step: function(delta) { if (this.encoder) { this.captureTimeout -= delta * 1000; if (this.captureTimeout <= 0) { this.captureTimeout = 1000 / this.framerate + this.captureTimeout; this.layer.drawImage(this.app.layer.canvas, this.region[0], this.region[1], this.region[2], this.region[3], 0, 0, this.layer.width, this.layer.height); this.encoder.add(this.layer.canvas); } this.app.screen.save().lineWidth(8).strokeStyle("#c00").strokeRect(0, 0, this.app.screen.width, this.app.screen.height).restore(); } }, stop: function() { if (!this.encoder) return; var output = this.encoder.compile(); var url = (window.webkitURL || window.URL).createObjectURL(output); window.open(url); this.recording = false; delete this.encoder; }, toggle: function(args) { if (this.encoder) this.stop(); else this.start(args); } };