/*     
  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);
  }

};