<commit_before>var svg;
var model;
var NUM_SERVERS = 5;
var RPC_TIMEOUT = 50000;
var RPC_LATENCY = 10000;
var ELECTION_TIMEOUT = 100000;
var ARC_WIDTH = 5;
var renderMessages;
var rules = {};

var util = {};

$(function() {

var makeElectionAlarm = function(model) {
  return model.time + (Math.random() + 1) * ELECTION_TIMEOUT;
};

model = {
  servers: [],
  messages: [],
  time: 0,
  seed: 0,
};

var makeLog = function() {
  var entries = [];
  return {
    entries: entries,
    at: function(index) {
      return entries[index - 1];
    },
    len: function() {
      return entries.length;
    },
    term: function(index) {
      if (index < 1 || index > entries.length) {
        return 0;
      } else {
        return entries[index - 1].term;
      }
    },
    slice: function(startIndexIncl, endIndexExcl) {
      return entries.slice(startIndexIncl - 1, endIndexExcl - 1);
    },
    append: function(entry) {
      entries.push(entry);
    },
    truncatePast: function(index) {
      entries = entries.slice(0, index);
    },
  };
};

util.value = function(v) {
  return function() { return v; };
};

util.circleCoord = function(frac, cx, cy, r) {
  var radians = 2 * Math.PI * (.75 + frac);
  return {
    x: cx + r * Math.cos(radians),
    y: cy + r * Math.sin(radians),
  };
};

util.countTrue = function(bools) {
  var count = 0;
  bools.forEach(function(b) {
    if (b)
      count += 1;
  });
  return count;
};

util.makeMap = function(keys, value) {
  var m = {};
  keys.forEach(function(key) {
    m[key] = value;
  });
  return m;
};

util.mapValues = function(m) {
  return $.map(m, function(v) { return v; });
};

var Server = function(id, peers) {
  return {
    id: id,
    peers: peers,
    state: 'follower',
    term: 1,
    votedFor: null,
    log: makeLog(),
    commitIndex: 0,
    electionAlarm: makeElectionAlarm(model),
    rpcDue:      util.makeMap(peers, 0),
    voteGranted: util.makeMap(peers, false),
    matchIndex:  util.makeMap(peers, 0),
    nextIndex:   util.makeMap(peers, 1),
  };
};

rules.startNewElection = function(model, server) {
  if ((server.state == 'follower' || server.state == 'candidate') &&
      server.electionAlarm < model.time) {
    server.electionAlarm = makeElectionAlarm(model);
    server.term += 1;
    server.votedFor = server.id;
    server.state = 'candidate';
    server.rpcDue      = util.makeMap(server.peers, 0);
    server.voteGranted = util.makeMap(server.peers, false);
    server.matchIndex  = util.makeMap(server.peers, 0);
    server.nextIndex   = util.makeMap(server.peers, 1);
  }
};

rules.sendRequestVote = function(model, server, peer) {
  if (server.state == 'candidate' &&
      server.rpcDue[peer] < model.time) {
    server.rpcDue[peer] = model.time + RPC_TIMEOUT;
    sendRequest(model, {
      from: server.id,
      to: peer,
      type: 'RequestVote',
      term: server.term,
      lastLogTerm: server.log.term(server.log.len()),
      lastLogIndex: server.log.len()});
  }
};

rules.becomeLeader = function(model, server) {
  if (server.state == 'candidate' &&
      util.countTrue(util.mapValues(server.voteGranted)) + 1 > Math.floor(NUM_SERVERS / 2)) {
    console.log('server ' + server.id + ' is leader in term ' + server.term);
    server.state = 'leader';
    server.nextIndex = util.makeMap(server.peers, server.log.len() + 1);
    server.rpcDue    = util.makeMap(server.peers, model.time);
    server.electionAlarm = Infinity;
  }
};

rules.sendAppendEntries = function(model, server, peer) {
  if (server.state == 'leader' &&
      (server.nextIndex[peer] < server.log.len() ||
       server.rpcDue[peer] < model.time)) {
    server.rpcDue[peer] = model.time + ELECTION_TIMEOUT / 2;
    var lastIndex = server.nextIndex[peer];
    if (lastIndex > server.log.len())
      lastIndex -= 1;
    server.nextIndex[peer] = lastIndex;
    sendRequest(model, {
      from: server.id,
      to: peer,
      type: 'AppendEntries',
      term: server.term,
      prevIndex: server.nextIndex[peer] - 1,
      prevTerm: server.log.term(server.nextIndex[peer] - 1),
      entries: server.log.slice(server.nextIndex[peer], lastIndex + 1),
      commitIndex: Math.min(server.commitIndex, lastIndex)});
  }
};

rules.advanceCommitIndex = function(model, server) {
  var matchIndexes = util.mapValues(server.matchIndex).concat(server.log.len());
  matchIndexes.sort();
  var n = matchIndexes[Math.floor(NUM_SERVERS / 2)];
  if (server.state == 'leader' &&
      server.log.term(n) == server.term) {
    server.commitIndex = n;
  }
}

var stepDown = function(model, server, term) {
  server.term = term;
  server.state = 'follower';
  server.votedFor = null;
  if (server.electionAlarm < model.time) {
    server.electionAlarm = makeElectionAlarm(model);
  }
};

var sendMessage = function(model, message) {
  message.sendTime = model.time;
  message.recvTime = model.time + RPC_LATENCY;
  model.messages.push(message);
};

var sendRequest = function(model, request) {
  request.direction = 'request';
  sendMessage(model, request);
};

var sendReply = function(model, request, reply) {
  reply.from = request.to;
  reply.to = request.from;
  reply.type = request.type;
  reply.direction = 'reply';
  sendMessage(model, reply);
};

var handleRequestVoteRequest = function(model, server, request) {
  if (server.term < request.term)
    stepDown(model, server, request.term);
  var granted = false;
  if (server.term == request.term &&
      (server.votedFor == null ||
       server.votedFor == request.from) &&
      (request.lastLogTerm > server.log.term(server.log.len()) ||
       (request.lastLogTerm == server.log.term(server.log.len()) &&
        request.lastLogIndex >= server.log.len()))) {
    granted = true;
    server.votedFor = request.from;
    server.electionAlarm = makeElectionAlarm(model);
  }
  sendReply(model, request, {
    term: server.term,
    granted: granted,
  });
};

var handleRequestVoteReply = function(model, server, reply) {
  if (server.term < reply.term)
    stepDown(reply.term);
  if (server.state == 'candidate' &&
      server.term == reply.term) {
    server.rpcDue[reply.from] = Infinity;
    server.voteGranted[reply.from] = reply.granted;
  }
}

var handleAppendEntriesRequest = function(model, server, request) {
  var success = false;
  var matchIndex = 0;
  if (server.term < request.term)
    stepDown(model, server, request.term);
  if (server.term == request.term) {
    server.state = 'follower';
    server.electionAlarm = makeElectionAlarm(model);
    if (request.prevLogIndex == 0 ||
        (request.prevIndex <= server.log.len() &&
         server.log.term(request.prevIndex) == request.prevTerm)) {
      success = true;
      var index = 0;
      for (var i = 0; i < request.entries.length; i += 1) {
        index = request.prevIndex + 1 + i;
        if (server.log.term(index) != request.entries[i].term) {
          server.log.truncatePast(index - 1);
          server.log.append(request.entries[i]);
        }
      }
      matchIndex = index;
      server.commitIndex = request.commitIndex;
    }
  }
  sendReply(model, request, {
    term: server.term,
    success: success,
    matchIndex: matchIndex,
  });
};

var handleAppendEntriesReply = function(model, server, reply) {
  if (server.term < reply.term)
    stepDown(reply.term);
  if (server.state == 'leader' &&
      server.term == reply.term) {
    if (reply.successs) {
      server.matchIndex[reply.from] = reply.matchIndex;
      server.nextIndex[reply.from] += 1;
    } else {
      server.nextIndex[reply.from] = Math.max(1, server.nextIndex[reply.from] - 1);
    }
  }
}

var handleMessage = function(model, server, message) {
  if (message.type == 'RequestVote') {
    if (message.direction == 'request')
      handleRequestVoteRequest(model, server, message);
    else
      handleRequestVoteReply(model, server, message);
  } else if (message.type == 'AppendEntries') {
    if (message.direction == 'request')
      handleAppendEntriesRequest(model, server, message);
    else
      handleAppendEntriesReply(model, server, message);
  }
};

(function() {
  for (var i = 1; i <= NUM_SERVERS; i += 1) {
      var peers = [];
      for (var j = 1; j <= NUM_SERVERS; j += 1) {
        if (i != j)
          peers.push(j);
      }
      model.servers.push(Server(i, peers));
  }
})();

svg = $('svg');

var ringSpec = {
  cx: 300,
  cy: 200,
  r: 150,
};

var serverSpec = function(id) {
  var coord = util.circleCoord((id - 1) / NUM_SERVERS,
                               ringSpec.cx, ringSpec.cy, ringSpec.r);
  return {
    cx: coord.x,
    cy: coord.y,
    r: 30,
  };
};

var ring = svg.append(
  $('<circle />')
    .attr('id', 'ring')
    .attr(ringSpec));

model.servers.forEach(function (server) {
  var s = serverSpec(server.id);
  svg.append(
    $('<g></g>')
      .attr('id', 'server-' + server.id)
      .attr('class', 'server')
      .append($('<circle />')
                 .attr(s))
      .append($('<path />')
                 .attr('style', 'stroke-width: ' + ARC_WIDTH))
      .append($('<text />')
                 .attr({x: s.cx, y: s.cy})));
});

util.reparseSVG = function() {
  svg.html(svg.html()); // reparse as SVG after adding nodes
};
util.reparseSVG();

var messageSpec = function(from, to, frac) {
  var fromSpec = serverSpec(from);
  var toSpec = serverSpec(to);
  // adjust frac so you start and end at the edge of servers
  var totalDist  = Math.sqrt(Math.pow(toSpec.cx - fromSpec.cx, 2) +
                             Math.pow(toSpec.cy - fromSpec.cy, 2));
  var travel = totalDist - fromSpec.r - toSpec.r;
  frac = (fromSpec.r / totalDist) + frac * (travel / totalDist);
  return {
    cx: fromSpec.cx + (toSpec.cx - fromSpec.cx) * frac,
    cy: fromSpec.cy + (toSpec.cy - fromSpec.cy) * frac,
    r: 5,
  };
};

var arcSpec = function(spec, fraction) {
  var comma = ',';
  var radius = spec.r + ARC_WIDTH/2;
  var end = util.circleCoord(fraction, spec.cx, spec.cy, radius);
  s = ['M', spec.cx, comma, spec.cy - radius];
  if (fraction > .5) {
    s.push('A', radius, comma, radius, '0 0,1', spec.cx, spec.cy + radius);
    s.push('M', spec.cx, comma, spec.cy + radius);
  }
  s.push('A', radius, comma, radius, '0 0,1', end.x, end.y);
  return s.join(' ');
};

renderServers = function() {
  model.servers.forEach(function(server) {
    var serverNode = $('#server-' + server.id, svg);
    $('circle', serverNode)
      .attr('class', server.state);
    $('path', serverNode)
      .attr('d', arcSpec(serverSpec(server.id),
              Math.min(1, (server.electionAlarm - model.time) /
                          (ELECTION_TIMEOUT * 2))));
    $('text', serverNode).text(server.term);
  });
};

renderMessages = function() {
  $('.message', svg).remove();
  model.messages.forEach(function(message) {
    var s = messageSpec(message.from, message.to,
                        (model.time - message.sendTime) /
                        (message.recvTime - message.sendTime));
    svg.append(
      $('<circle />')
        .attr('class', 'message ' + message.direction)
        .attr(s));
  });
  util.reparseSVG();
};

setInterval(function() {
  model.time += 100;
  model.servers.forEach(function(server) {
    rules.startNewElection(model, server);
    rules.becomeLeader(model, server);
    rules.advanceCommitIndex(model, server);
    server.peers.forEach(function(peer) {
      rules.sendRequestVote(model, server, peer);
      rules.sendAppendEntries(model, server, peer);
    });
  });
  var deliver = [];
  var keep = [];
  model.messages.forEach(function(message) {
    if (message.recvTime <= model.time)
      deliver.push(message);
    else
      keep.push(message);
  });
  model.messages = keep;
  deliver.forEach(function(message) {
    model.servers.forEach(function(server) {
      if (server.id == message.to) {
        handleMessage(model, server, message);
      }
    });
  });

  renderServers();
  renderMessages();
}, 10);

});
<commit_msg>Add random jitter to messages<commit_after>var svg;
var model;
var NUM_SERVERS = 5;
var RPC_TIMEOUT = 50000;
var RPC_LATENCY = 10000;
var ELECTION_TIMEOUT = 100000;
var ARC_WIDTH = 5;
var renderMessages;
var rules = {};

var util = {};

$(function() {

var makeElectionAlarm = function(model) {
  return model.time + (Math.random() + 1) * ELECTION_TIMEOUT;
};

model = {
  servers: [],
  messages: [],
  time: 0,
  seed: 0,
};

var makeLog = function() {
  var entries = [];
  return {
    entries: entries,
    at: function(index) {
      return entries[index - 1];
    },
    len: function() {
      return entries.length;
    },
    term: function(index) {
      if (index < 1 || index > entries.length) {
        return 0;
      } else {
        return entries[index - 1].term;
      }
    },
    slice: function(startIndexIncl, endIndexExcl) {
      return entries.slice(startIndexIncl - 1, endIndexExcl - 1);
    },
    append: function(entry) {
      entries.push(entry);
    },
    truncatePast: function(index) {
      entries = entries.slice(0, index);
    },
  };
};

util.value = function(v) {
  return function() { return v; };
};

util.circleCoord = function(frac, cx, cy, r) {
  var radians = 2 * Math.PI * (.75 + frac);
  return {
    x: cx + r * Math.cos(radians),
    y: cy + r * Math.sin(radians),
  };
};

util.countTrue = function(bools) {
  var count = 0;
  bools.forEach(function(b) {
    if (b)
      count += 1;
  });
  return count;
};

util.makeMap = function(keys, value) {
  var m = {};
  keys.forEach(function(key) {
    m[key] = value;
  });
  return m;
};

util.mapValues = function(m) {
  return $.map(m, function(v) { return v; });
};

var Server = function(id, peers) {
  return {
    id: id,
    peers: peers,
    state: 'follower',
    term: 1,
    votedFor: null,
    log: makeLog(),
    commitIndex: 0,
    electionAlarm: makeElectionAlarm(model),
    rpcDue:      util.makeMap(peers, 0),
    voteGranted: util.makeMap(peers, false),
    matchIndex:  util.makeMap(peers, 0),
    nextIndex:   util.makeMap(peers, 1),
  };
};

rules.startNewElection = function(model, server) {
  if ((server.state == 'follower' || server.state == 'candidate') &&
      server.electionAlarm < model.time) {
    server.electionAlarm = makeElectionAlarm(model);
    server.term += 1;
    server.votedFor = server.id;
    server.state = 'candidate';
    server.rpcDue      = util.makeMap(server.peers, 0);
    server.voteGranted = util.makeMap(server.peers, false);
    server.matchIndex  = util.makeMap(server.peers, 0);
    server.nextIndex   = util.makeMap(server.peers, 1);
  }
};

rules.sendRequestVote = function(model, server, peer) {
  if (server.state == 'candidate' &&
      server.rpcDue[peer] < model.time) {
    server.rpcDue[peer] = model.time + RPC_TIMEOUT;
    sendRequest(model, {
      from: server.id,
      to: peer,
      type: 'RequestVote',
      term: server.term,
      lastLogTerm: server.log.term(server.log.len()),
      lastLogIndex: server.log.len()});
  }
};

rules.becomeLeader = function(model, server) {
  if (server.state == 'candidate' &&
      util.countTrue(util.mapValues(server.voteGranted)) + 1 > Math.floor(NUM_SERVERS / 2)) {
    console.log('server ' + server.id + ' is leader in term ' + server.term);
    server.state = 'leader';
    server.nextIndex = util.makeMap(server.peers, server.log.len() + 1);
    server.rpcDue    = util.makeMap(server.peers, model.time);
    server.electionAlarm = Infinity;
  }
};

rules.sendAppendEntries = function(model, server, peer) {
  if (server.state == 'leader' &&
      (server.nextIndex[peer] < server.log.len() ||
       server.rpcDue[peer] < model.time)) {
    server.rpcDue[peer] = model.time + ELECTION_TIMEOUT / 2;
    var lastIndex = server.nextIndex[peer];
    if (lastIndex > server.log.len())
      lastIndex -= 1;
    server.nextIndex[peer] = lastIndex;
    sendRequest(model, {
      from: server.id,
      to: peer,
      type: 'AppendEntries',
      term: server.term,
      prevIndex: server.nextIndex[peer] - 1,
      prevTerm: server.log.term(server.nextIndex[peer] - 1),
      entries: server.log.slice(server.nextIndex[peer], lastIndex + 1),
      commitIndex: Math.min(server.commitIndex, lastIndex)});
  }
};

rules.advanceCommitIndex = function(model, server) {
  var matchIndexes = util.mapValues(server.matchIndex).concat(server.log.len());
  matchIndexes.sort();
  var n = matchIndexes[Math.floor(NUM_SERVERS / 2)];
  if (server.state == 'leader' &&
      server.log.term(n) == server.term) {
    server.commitIndex = n;
  }
}

var stepDown = function(model, server, term) {
  server.term = term;
  server.state = 'follower';
  server.votedFor = null;
  if (server.electionAlarm < model.time) {
    server.electionAlarm = makeElectionAlarm(model);
  }
};

var sendMessage = function(model, message) {
  message.sendTime = model.time;
  message.recvTime = model.time + (1 + (.5 * (Math.random() - .5))) * RPC_LATENCY;
  model.messages.push(message);
};

var sendRequest = function(model, request) {
  request.direction = 'request';
  sendMessage(model, request);
};

var sendReply = function(model, request, reply) {
  reply.from = request.to;
  reply.to = request.from;
  reply.type = request.type;
  reply.direction = 'reply';
  sendMessage(model, reply);
};

var handleRequestVoteRequest = function(model, server, request) {
  if (server.term < request.term)
    stepDown(model, server, request.term);
  var granted = false;
  if (server.term == request.term &&
      (server.votedFor == null ||
       server.votedFor == request.from) &&
      (request.lastLogTerm > server.log.term(server.log.len()) ||
       (request.lastLogTerm == server.log.term(server.log.len()) &&
        request.lastLogIndex >= server.log.len()))) {
    granted = true;
    server.votedFor = request.from;
    server.electionAlarm = makeElectionAlarm(model);
  }
  sendReply(model, request, {
    term: server.term,
    granted: granted,
  });
};

var handleRequestVoteReply = function(model, server, reply) {
  if (server.term < reply.term)
    stepDown(reply.term);
  if (server.state == 'candidate' &&
      server.term == reply.term) {
    server.rpcDue[reply.from] = Infinity;
    server.voteGranted[reply.from] = reply.granted;
  }
}

var handleAppendEntriesRequest = function(model, server, request) {
  var success = false;
  var matchIndex = 0;
  if (server.term < request.term)
    stepDown(model, server, request.term);
  if (server.term == request.term) {
    server.state = 'follower';
    server.electionAlarm = makeElectionAlarm(model);
    if (request.prevLogIndex == 0 ||
        (request.prevIndex <= server.log.len() &&
         server.log.term(request.prevIndex) == request.prevTerm)) {
      success = true;
      var index = 0;
      for (var i = 0; i < request.entries.length; i += 1) {
        index = request.prevIndex + 1 + i;
        if (server.log.term(index) != request.entries[i].term) {
          server.log.truncatePast(index - 1);
          server.log.append(request.entries[i]);
        }
      }
      matchIndex = index;
      server.commitIndex = request.commitIndex;
    }
  }
  sendReply(model, request, {
    term: server.term,
    success: success,
    matchIndex: matchIndex,
  });
};

var handleAppendEntriesReply = function(model, server, reply) {
  if (server.term < reply.term)
    stepDown(reply.term);
  if (server.state == 'leader' &&
      server.term == reply.term) {
    if (reply.successs) {
      server.matchIndex[reply.from] = reply.matchIndex;
      server.nextIndex[reply.from] += 1;
    } else {
      server.nextIndex[reply.from] = Math.max(1, server.nextIndex[reply.from] - 1);
    }
  }
}

var handleMessage = function(model, server, message) {
  if (message.type == 'RequestVote') {
    if (message.direction == 'request')
      handleRequestVoteRequest(model, server, message);
    else
      handleRequestVoteReply(model, server, message);
  } else if (message.type == 'AppendEntries') {
    if (message.direction == 'request')
      handleAppendEntriesRequest(model, server, message);
    else
      handleAppendEntriesReply(model, server, message);
  }
};

(function() {
  for (var i = 1; i <= NUM_SERVERS; i += 1) {
      var peers = [];
      for (var j = 1; j <= NUM_SERVERS; j += 1) {
        if (i != j)
          peers.push(j);
      }
      model.servers.push(Server(i, peers));
  }
})();

svg = $('svg');

var ringSpec = {
  cx: 300,
  cy: 200,
  r: 150,
};

var serverSpec = function(id) {
  var coord = util.circleCoord((id - 1) / NUM_SERVERS,
                               ringSpec.cx, ringSpec.cy, ringSpec.r);
  return {
    cx: coord.x,
    cy: coord.y,
    r: 30,
  };
};

var ring = svg.append(
  $('<circle />')
    .attr('id', 'ring')
    .attr(ringSpec));

model.servers.forEach(function (server) {
  var s = serverSpec(server.id);
  svg.append(
    $('<g></g>')
      .attr('id', 'server-' + server.id)
      .attr('class', 'server')
      .append($('<circle />')
                 .attr(s))
      .append($('<path />')
                 .attr('style', 'stroke-width: ' + ARC_WIDTH))
      .append($('<text />')
                 .attr({x: s.cx, y: s.cy})));
});

util.reparseSVG = function() {
  svg.html(svg.html()); // reparse as SVG after adding nodes
};
util.reparseSVG();

var messageSpec = function(from, to, frac) {
  var fromSpec = serverSpec(from);
  var toSpec = serverSpec(to);
  // adjust frac so you start and end at the edge of servers
  var totalDist  = Math.sqrt(Math.pow(toSpec.cx - fromSpec.cx, 2) +
                             Math.pow(toSpec.cy - fromSpec.cy, 2));
  var travel = totalDist - fromSpec.r - toSpec.r;
  frac = (fromSpec.r / totalDist) + frac * (travel / totalDist);
  return {
    cx: fromSpec.cx + (toSpec.cx - fromSpec.cx) * frac,
    cy: fromSpec.cy + (toSpec.cy - fromSpec.cy) * frac,
    r: 5,
  };
};

var arcSpec = function(spec, fraction) {
  var comma = ',';
  var radius = spec.r + ARC_WIDTH/2;
  var end = util.circleCoord(fraction, spec.cx, spec.cy, radius);
  s = ['M', spec.cx, comma, spec.cy - radius];
  if (fraction > .5) {
    s.push('A', radius, comma, radius, '0 0,1', spec.cx, spec.cy + radius);
    s.push('M', spec.cx, comma, spec.cy + radius);
  }
  s.push('A', radius, comma, radius, '0 0,1', end.x, end.y);
  return s.join(' ');
};

renderServers = function() {
  model.servers.forEach(function(server) {
    var serverNode = $('#server-' + server.id, svg);
    $('circle', serverNode)
      .attr('class', server.state);
    $('path', serverNode)
      .attr('d', arcSpec(serverSpec(server.id),
              Math.min(1, (server.electionAlarm - model.time) /
                          (ELECTION_TIMEOUT * 2))));
    $('text', serverNode).text(server.term);
  });
};

renderMessages = function() {
  $('.message', svg).remove();
  model.messages.forEach(function(message) {
    var s = messageSpec(message.from, message.to,
                        (model.time - message.sendTime) /
                        (message.recvTime - message.sendTime));
    svg.append(
      $('<circle />')
        .attr('class', 'message ' + message.direction)
        .attr(s));
  });
  util.reparseSVG();
};

setInterval(function() {
  model.time += 100;
  model.servers.forEach(function(server) {
    rules.startNewElection(model, server);
    rules.becomeLeader(model, server);
    rules.advanceCommitIndex(model, server);
    server.peers.forEach(function(peer) {
      rules.sendRequestVote(model, server, peer);
      rules.sendAppendEntries(model, server, peer);
    });
  });
  var deliver = [];
  var keep = [];
  model.messages.forEach(function(message) {
    if (message.recvTime <= model.time)
      deliver.push(message);
    else
      keep.push(message);
  });
  model.messages = keep;
  deliver.forEach(function(message) {
    model.servers.forEach(function(server) {
      if (server.id == message.to) {
        handleMessage(model, server, message);
      }
    });
  });

  renderServers();
  renderMessages();
}, 10);

});
<|endoftext|>


<commit_before>chrome.extension.sendMessage({}, function(response) {
  var settings = {
    // オプションで変更可能なキーコード
    togglePlayAndPauseKeyCode:      112,  // default: p
    jumpToBeginningKeyCode:         104,  // defualt: h
    jumpToEndKeyCode:               101,  // default: e
    rewindTimeKeyCode:               97,  // default: a
    advanceTimeKeyCode:             115,  // default: s
    speedDownKeyCode:               100,  // default: d
    speedUpKeyCode:                 117,  // default: u
    resetSpeedKeyCode:              114,  // default: r
    partialLoopKeyCode:             108,  // default: l
    skipTimeAmount:                   5,  // default: 5 sec
  };
  var fixed = {
    // 固定のキーコード
    togglePlayAndPauseKeyCode: 32,  // space
    rewindTimeKeyCode:         37,  // left-arrow
    advanceTimeKeyCode:        39,  // right-arrow
    isEscape:                  27,  // esc
  };

  chrome.storage.sync.get(settings, function(storage) {
    settings.togglePlayAndPauseKeyCode = Number(storage.togglePlayAndPauseKeyCode);
    settings.jumpToBeginningKeyCode    = Number(storage.jumpToBeginningKeyCode);
    settings.jumpToEndKeyCode          = Number(storage.jumpToEndKeyCode);
    settings.rewindTimeKeyCode         = Number(storage.rewindTimeKeyCode);
    settings.advanceTimeKeyCode        = Number(storage.advanceTimeKeyCode);
    settings.speedDownKeyCode          = Number(storage.speedDownKeyCode);
    settings.speedUpKeyCode            = Number(storage.speedUpKeyCode);
    settings.resetSpeedKeyCode         = Number(storage.resetSpeedKeyCode);
    settings.partialLoopKeyCode        = Number(storage.partialLoopKeyCode);
    settings.skipTimeAmount            = Number(storage.skipTimeAmount);
  });

  // グローバル変数
  // loopStatus
  //   0 -> Not set
  //   1 -> Set
  //   2 -> Loop
  //   3 -> Restore
  var player;
  var loopStatus = 0;
  var loopStart;
  var loopEnd;
  var loopFlag = false;
  var loopTimeoutID;
  var removeStatusBoxTimeoutID;
  var getVideoTimeoutID;

  // video要素を取得する
  function getVideoElement() {
    if (document.getElementsByTagName('video')[0] !== undefined) {
      player = document.getElementsByTagName('video')[0];
      clearTimeout(getVideoTimeoutID);
      observeSpeed();
      return false;
    }
    getVideoTimeoutID = setTimeout(function() {
      getVideoElement();
    }, 10);
  };
  getVideoElement();

  // オプションのキーが押されたかどうかを判定
  window.addEventListener('keypress', function(event) {
    // 動画がないときはキーイベントを実行しない
    if (player === undefined) {
      return false;
    }

    // オプションのキーと固定のキーに関しては
    // 元々サイトで実装されているイベントリスナーを
    // 無効化してこちらの処理のみを実行する
    Object.keys(settings).forEach(function(key) {
      if (event.keyCode == settings[key]) {
        event.stopPropagation();
      }
    });

    // 入力フォームにフォーカスがあるときはショートカットを無効化
    if ((document.activeElement.nodeName === 'INPUT'
    || document.activeElement.nodeName === 'TEXTAREA'
    || document.activeElement.getAttribute('type') === 'text')
    || document.activeElement.isContentEditable === true) {
      return false;
    }
    else {
      activeBlur();
    }

    // cmd, ctrl, alt をエスケープ
    if (event.metaKey || event.ctrlKey || event.altKey) {
      return false;
    }

    // ショートカットキーから関数を呼び出す
    switch (event.keyCode) {
      // オプションのキーコード
      case settings.togglePlayAndPauseKeyCode: togglePlayAndPause(); break;  // default: p
      case settings.jumpToBeginningKeyCode:    jumpToBeginning();    break;  // default: h
      case settings.jumpToEndKeyCode:          jumpToEnd();          break;  // default: e
      case settings.rewindTimeKeyCode:         rewindTime();         break;  // default: a
      case settings.advanceTimeKeyCode:        advanceTime();        break;  // default: s
      case settings.speedDownKeyCode:          speedDown();          break;  // default: d
      case settings.speedUpKeyCode:            speedUp();            break;  // default: u
      case settings.resetSpeedKeyCode:         resetSpeed();         break;  // default: r
    }

    // 数字のキーを押すとその数字に対応する割合まで動画を移動する
    // キーボードの 3 を押すと動画全体の 30% の位置に移動する
    // 固定のキーコード
    if (event.keyCode >= 48 && event.keyCode <= 57) {
      jumpToTimerRatio(event.keyCode);
    }

    // 部分ループ再生のステータスを記録
    // オプションで変更可能なキーコード
    // default: L
    if (event.keyCode == settings.partialLoopKeyCode) {
      if (loopStatus === undefined) {
        loopStatus = 0;
      }
      loopStatus++;
      partialLoop();
    }
  }, true);

  // 固定のキーが押されたかどうかを判定
  window.addEventListener('keydown', function(event) {
    if (player === undefined) {
      return false;
    }

    Object.keys(fixed).forEach(function(key) {
      if (event.keyCode == fixed[key]) {
        event.stopPropagation();
      }
    });

    if ((document.activeElement.nodeName === 'INPUT'
    || document.activeElement.nodeName === 'TEXTAREA'
    || document.activeElement.getAttribute('type') === 'text')
    || document.activeElement.isContentEditable === true) {
      return false;
    }
    else {
      activeBlur();
    }

    if (event.metaKey || event.shiftKey || event.ctrlKey || event.altKey) {
      return false;
    }

    switch (event.keyCode) {
      // 固定のキーコード
      case fixed.togglePlayAndPauseKeyCode: event.preventDefault(); togglePlayAndPause(); break;  // space
      case fixed.rewindTimeKeyCode:         event.preventDefault(); rewindTime();         break;  // left-arrow
      case fixed.advanceTimeKeyCode:        event.preventDefault(); advanceTime();        break;  // right-arrow
      case fixed.isEscape:                  activeBlur();           resetLoopStatus();    break;  // esc
    }
  }, true);

  // 再生スピードの変更を監視する
  function observeSpeed() {
    player.onratechange = function() {
      setSpeedRange(player.playbackRate);
    };
  };

  // 再生/停止
  function togglePlayAndPause() {
    if (player.paused === true)
      player.play();
    else
      player.pause();
    scrollToPlayer();
  };

  // 数秒巻き戻し
  function rewindTime() {
    player.currentTime -= settings.skipTimeAmount;
    scrollToPlayer();
  };

  // 数秒早送り
  function advanceTime() {
    player.currentTime += settings.skipTimeAmount;
    scrollToPlayer();
  };

  // 再生スピードダウン
  function speedDown() {
    player.playbackRate = floorFormat((player.playbackRate - 0.09), 1);
    statusBox('playbackRate', adjustSpeedStatus(player.playbackRate));
    scrollToPlayer();
  }

  // 再生スピードアップ
  function speedUp() {
    player.playbackRate = floorFormat((player.playbackRate + 0.11), 1);
    statusBox('playbackRate', adjustSpeedStatus(player.playbackRate));
    scrollToPlayer();
  }

  // 再生スピードリセット
  function resetSpeed() {
    player.playbackRate = 1.0;
    statusBox('playbackRate', adjustSpeedStatus(player.playbackRate));
    scrollToPlayer();
  }

  // 動画の最初の位置に移動する
  function jumpToBeginning() {
    player.currentTime = player.seekable.start(0);
    scrollToPlayer();
  };

  // 動画の最後の位置に移動する
  function jumpToEnd() {
    player.currentTime = player.seekable.end(0);
    scrollToPlayer();
  };

  // 数字に対応する割合まで動画を移動する
  function jumpToTimerRatio(timerRatio) {
    timerRatio = (timerRatio - 48) / 10;
    player.currentTime = player.seekable.end(0) * timerRatio;
    scrollToPlayer();
  };

  // 部分ループ再生
  function partialLoop() {
    if (loopStatus === 1) {
      loopStart = player.currentTime;
      statusBox('set', 'Set');
    }
    else if (loopStatus === 2) {
      if (loopFlag === false) {
        loopEnd = player.currentTime;
        loopFlag = true;
        statusBox('loop', 'Loop!');
      }
      loopTimeoutID = setTimeout(function() {
        if (player.currentTime >= loopEnd || player.currentTime < loopStart) {
          player.currentTime = loopStart;
        }
        if (loopStatus === 3) {
          clearTimeout(loopTimeoutID);
          loopStatus = 0;
          loopFlag = false;
          statusBox('restore', 'Restore');
          return false;
        }
        partialLoop();
      }, 100);
    }
  };

  // アクティブフォーカスを外す
  function activeBlur() {
    document.activeElement.blur();
  };

  // ループステータスをリセットする
  function resetLoopStatus() {
    if (loopStatus !== 0) {
      clearTimeout(loopTimeoutID);
      loopStatus = 0;
      loopFlag = false;
      statusBox('reset', 'Restore');
    }
  };

  // 小数点第(n+1)位を切り捨てて小数点第n位まで求める
  function floorFormat(number, n) {
    var _pow = Math.pow(10, n);
    return Math.floor(number * _pow) / _pow;
  };

  // 動画プレイヤーのある位置までスクロールする
  function scrollToPlayer() {
    var rect = player.getBoundingClientRect();
    var positionY = rect.top;
    var dElm = document.documentElement;
    var dBody = document.body;
    var scrollY = dElm.scrollTop || dBody.scrollTop;
    var y = positionY + scrollY - 100;
    window.scrollTo(0, y);
  };

  // 部分ループ再生のステータスを表示する
  function statusBox(status, statusStr) {
    if (status === 'set') {
      var videoStatus = '' + statusStr + '';
    }
    else {
      var videoStatus = '' + statusStr + '';
    }

    $('.video-status').remove();
    if (status === 'loop') {
      $('.video-status-keep-showing').remove();
    }
    if (status === 'reset') {
      $('.video-status-keep-showing').remove();
      clearTimeout(removeStatusBoxTimeoutID);
    }

    $(videoStatus).insertBefore('video');

    if ($('span').hasClass('video-status') && $('span').hasClass('video-status-keep-showing')) {
      $('.video-status-keep-showing').css('display', 'none');
      clearTimeout(removeStatusBoxTimeoutID);
      removeStatusBoxTimeoutID = setTimeout(function() {
        $('.video-status').remove();
        $('.video-status-keep-showing').css('display', 'inline-block');
      }, 1000);
    }
    else {
      $('.video-status').fadeOut(1000, function() {
        $(this).remove();
      });
    }
  };

  // 再生スピードの上限と下限を設定する
  function setSpeedRange(speedChecker) {
    if (speedChecker < 0.5) {
      player.playbackRate = 0.5;
    }
    else if (speedChecker > 4.0) {
      player.playbackRate = 4.0;
    }
  };

  // 表示される再生スピードを調整する
  function adjustSpeedStatus(speedStatus) {
    if (speedStatus < 0.5) {
      speedStatus = 0.5;
    }
    else if (speedStatus > 4.0) {
      speedStatus = 4.0;
    }
    return speedStatus.toFixed(1);
  };
});
<commit_msg>:recycle: Change a comment<commit_after>chrome.extension.sendMessage({}, function(response) {
  var settings = {
    // オプションで変更可能なキーコード
    togglePlayAndPauseKeyCode:      112,  // default: p
    jumpToBeginningKeyCode:         104,  // defualt: h
    jumpToEndKeyCode:               101,  // default: e
    rewindTimeKeyCode:               97,  // default: a
    advanceTimeKeyCode:             115,  // default: s
    speedDownKeyCode:               100,  // default: d
    speedUpKeyCode:                 117,  // default: u
    resetSpeedKeyCode:              114,  // default: r
    partialLoopKeyCode:             108,  // default: l
    skipTimeAmount:                   5,  // default: 5 sec
  };
  var fixed = {
    // 固定のキーコード
    togglePlayAndPauseKeyCode: 32,  // space
    rewindTimeKeyCode:         37,  // left-arrow
    advanceTimeKeyCode:        39,  // right-arrow
    isEscape:                  27,  // esc
  };

  chrome.storage.sync.get(settings, function(storage) {
    settings.togglePlayAndPauseKeyCode = Number(storage.togglePlayAndPauseKeyCode);
    settings.jumpToBeginningKeyCode    = Number(storage.jumpToBeginningKeyCode);
    settings.jumpToEndKeyCode          = Number(storage.jumpToEndKeyCode);
    settings.rewindTimeKeyCode         = Number(storage.rewindTimeKeyCode);
    settings.advanceTimeKeyCode        = Number(storage.advanceTimeKeyCode);
    settings.speedDownKeyCode          = Number(storage.speedDownKeyCode);
    settings.speedUpKeyCode            = Number(storage.speedUpKeyCode);
    settings.resetSpeedKeyCode         = Number(storage.resetSpeedKeyCode);
    settings.partialLoopKeyCode        = Number(storage.partialLoopKeyCode);
    settings.skipTimeAmount            = Number(storage.skipTimeAmount);
  });

  // グローバル変数
  // loopStatus
  //   0 -> Not set
  //   1 -> Set
  //   2 -> Loop
  //   3 -> Restore
  var player;
  var loopStatus = 0;
  var loopStart;
  var loopEnd;
  var loopFlag = false;
  var loopTimeoutID;
  var removeStatusBoxTimeoutID;
  var getVideoTimeoutID;

  // video要素を取得する
  function getVideoElement() {
    if (document.getElementsByTagName('video')[0] !== undefined) {
      player = document.getElementsByTagName('video')[0];
      clearTimeout(getVideoTimeoutID);
      observeSpeed();
      return false;
    }
    getVideoTimeoutID = setTimeout(function() {
      getVideoElement();
    }, 10);
  };
  getVideoElement();

  // オプションのキーが押されたかどうかを判定
  window.addEventListener('keypress', function(event) {
    // 動画がないときはキーイベントを実行しない
    if (player === undefined) {
      return false;
    }

    // オプションのキーと固定のキーに関しては
    // 元々サイトで実装されているイベントリスナーを
    // 無効化してこちらの処理のみを実行する
    Object.keys(settings).forEach(function(key) {
      if (event.keyCode == settings[key]) {
        event.stopPropagation();
      }
    });

    // 入力フォームにフォーカスがあるときはショートカットを無効化
    if ((document.activeElement.nodeName === 'INPUT'
    || document.activeElement.nodeName === 'TEXTAREA'
    || document.activeElement.getAttribute('type') === 'text')
    || document.activeElement.isContentEditable === true) {
      return false;
    }
    else {
      activeBlur();
    }

    // 修飾キーをエスケープ
    if (event.metaKey || event.ctrlKey || event.altKey) {
      return false;
    }

    // ショートカットキーから関数を呼び出す
    switch (event.keyCode) {
      // オプションのキーコード
      case settings.togglePlayAndPauseKeyCode: togglePlayAndPause(); break;  // default: p
      case settings.jumpToBeginningKeyCode:    jumpToBeginning();    break;  // default: h
      case settings.jumpToEndKeyCode:          jumpToEnd();          break;  // default: e
      case settings.rewindTimeKeyCode:         rewindTime();         break;  // default: a
      case settings.advanceTimeKeyCode:        advanceTime();        break;  // default: s
      case settings.speedDownKeyCode:          speedDown();          break;  // default: d
      case settings.speedUpKeyCode:            speedUp();            break;  // default: u
      case settings.resetSpeedKeyCode:         resetSpeed();         break;  // default: r
    }

    // 数字のキーを押すとその数字に対応する割合まで動画を移動する
    // キーボードの 3 を押すと動画全体の 30% の位置に移動する
    // 固定のキーコード
    if (event.keyCode >= 48 && event.keyCode <= 57) {
      jumpToTimerRatio(event.keyCode);
    }

    // 部分ループ再生のステータスを記録
    // オプションで変更可能なキーコード
    // default: L
    if (event.keyCode == settings.partialLoopKeyCode) {
      if (loopStatus === undefined) {
        loopStatus = 0;
      }
      loopStatus++;
      partialLoop();
    }
  }, true);

  // 固定のキーが押されたかどうかを判定
  window.addEventListener('keydown', function(event) {
    if (player === undefined) {
      return false;
    }

    Object.keys(fixed).forEach(function(key) {
      if (event.keyCode == fixed[key]) {
        event.stopPropagation();
      }
    });

    if ((document.activeElement.nodeName === 'INPUT'
    || document.activeElement.nodeName === 'TEXTAREA'
    || document.activeElement.getAttribute('type') === 'text')
    || document.activeElement.isContentEditable === true) {
      return false;
    }
    else {
      activeBlur();
    }

    if (event.metaKey || event.shiftKey || event.ctrlKey || event.altKey) {
      return false;
    }

    switch (event.keyCode) {
      // 固定のキーコード
      case fixed.togglePlayAndPauseKeyCode: event.preventDefault(); togglePlayAndPause(); break;  // space
      case fixed.rewindTimeKeyCode:         event.preventDefault(); rewindTime();         break;  // left-arrow
      case fixed.advanceTimeKeyCode:        event.preventDefault(); advanceTime();        break;  // right-arrow
      case fixed.isEscape:                  activeBlur();           resetLoopStatus();    break;  // esc
    }
  }, true);

  // 再生スピードの変更を監視する
  function observeSpeed() {
    player.onratechange = function() {
      setSpeedRange(player.playbackRate);
    };
  };

  // 再生/停止
  function togglePlayAndPause() {
    if (player.paused === true)
      player.play();
    else
      player.pause();
    scrollToPlayer();
  };

  // 数秒巻き戻し
  function rewindTime() {
    player.currentTime -= settings.skipTimeAmount;
    scrollToPlayer();
  };

  // 数秒早送り
  function advanceTime() {
    player.currentTime += settings.skipTimeAmount;
    scrollToPlayer();
  };

  // 再生スピードダウン
  function speedDown() {
    player.playbackRate = floorFormat((player.playbackRate - 0.09), 1);
    statusBox('playbackRate', adjustSpeedStatus(player.playbackRate));
    scrollToPlayer();
  }

  // 再生スピードアップ
  function speedUp() {
    player.playbackRate = floorFormat((player.playbackRate + 0.11), 1);
    statusBox('playbackRate', adjustSpeedStatus(player.playbackRate));
    scrollToPlayer();
  }

  // 再生スピードリセット
  function resetSpeed() {
    player.playbackRate = 1.0;
    statusBox('playbackRate', adjustSpeedStatus(player.playbackRate));
    scrollToPlayer();
  }

  // 動画の最初の位置に移動する
  function jumpToBeginning() {
    player.currentTime = player.seekable.start(0);
    scrollToPlayer();
  };

  // 動画の最後の位置に移動する
  function jumpToEnd() {
    player.currentTime = player.seekable.end(0);
    scrollToPlayer();
  };

  // 数字に対応する割合まで動画を移動する
  function jumpToTimerRatio(timerRatio) {
    timerRatio = (timerRatio - 48) / 10;
    player.currentTime = player.seekable.end(0) * timerRatio;
    scrollToPlayer();
  };

  // 部分ループ再生
  function partialLoop() {
    if (loopStatus === 1) {
      loopStart = player.currentTime;
      statusBox('set', 'Set');
    }
    else if (loopStatus === 2) {
      if (loopFlag === false) {
        loopEnd = player.currentTime;
        loopFlag = true;
        statusBox('loop', 'Loop!');
      }
      loopTimeoutID = setTimeout(function() {
        if (player.currentTime >= loopEnd || player.currentTime < loopStart) {
          player.currentTime = loopStart;
        }
        if (loopStatus === 3) {
          clearTimeout(loopTimeoutID);
          loopStatus = 0;
          loopFlag = false;
          statusBox('restore', 'Restore');
          return false;
        }
        partialLoop();
      }, 100);
    }
  };

  // アクティブフォーカスを外す
  function activeBlur() {
    document.activeElement.blur();
  };

  // ループステータスをリセットする
  function resetLoopStatus() {
    if (loopStatus !== 0) {
      clearTimeout(loopTimeoutID);
      loopStatus = 0;
      loopFlag = false;
      statusBox('reset', 'Restore');
    }
  };

  // 小数点第(n+1)位を切り捨てて小数点第n位まで求める
  function floorFormat(number, n) {
    var _pow = Math.pow(10, n);
    return Math.floor(number * _pow) / _pow;
  };

  // 動画プレイヤーのある位置までスクロールする
  function scrollToPlayer() {
    var rect = player.getBoundingClientRect();
    var positionY = rect.top;
    var dElm = document.documentElement;
    var dBody = document.body;
    var scrollY = dElm.scrollTop || dBody.scrollTop;
    var y = positionY + scrollY - 100;
    window.scrollTo(0, y);
  };

  // 部分ループ再生のステータスを表示する
  function statusBox(status, statusStr) {
    if (status === 'set') {
      var videoStatus = '' + statusStr + '';
    }
    else {
      var videoStatus = '' + statusStr + '';
    }

    $('.video-status').remove();
    if (status === 'loop') {
      $('.video-status-keep-showing').remove();
    }
    if (status === 'reset') {
      $('.video-status-keep-showing').remove();
      clearTimeout(removeStatusBoxTimeoutID);
    }

    $(videoStatus).insertBefore('video');

    if ($('span').hasClass('video-status') && $('span').hasClass('video-status-keep-showing')) {
      $('.video-status-keep-showing').css('display', 'none');
      clearTimeout(removeStatusBoxTimeoutID);
      removeStatusBoxTimeoutID = setTimeout(function() {
        $('.video-status').remove();
        $('.video-status-keep-showing').css('display', 'inline-block');
      }, 1000);
    }
    else {
      $('.video-status').fadeOut(1000, function() {
        $(this).remove();
      });
    }
  };

  // 再生スピードの上限と下限を設定する
  function setSpeedRange(speedChecker) {
    if (speedChecker < 0.5) {
      player.playbackRate = 0.5;
    }
    else if (speedChecker > 4.0) {
      player.playbackRate = 4.0;
    }
  };

  // 表示される再生スピードを調整する
  function adjustSpeedStatus(speedStatus) {
    if (speedStatus < 0.5) {
      speedStatus = 0.5;
    }
    else if (speedStatus > 4.0) {
      speedStatus = 4.0;
    }
    return speedStatus.toFixed(1);
  };
});
<|endoftext|>


<commit_before>/**
 * Smartmirror
 * ---
 * @author Michael Pomogajko
 * @version 0.1
 * ---
 * Note: Read the README!
 * ---
 */

var months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "November", "Dezember"];

var days = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];

function getStations(data){
    var buff = [];

    var str = data.substring(data.indexOf("<span class=\"stand"), data.indexOf("<div id=\"mobil_impressum"));

    $($(str)[2]).find('td').each(function(){
        buff.push(this);
    });

    var deps = [];

    for (var i = 0; i < 30; i++) {
        if(i%3 === 0){
            var dep = {};
            dep.line = buff[i].innerText;
            dep.to = buff[i+1].innerText;
            dep.time = buff[i+2].innerText;
            deps.push(dep);
        }
    }
    fillStations(deps);
}

function fillStations(stations){
    var table = $("#tbody");
    table.html("");

    var destset = ["Weiden West", "Junkersdorf", "Universität", "Sülz"];

    stations.forEach(function(stat){
        if(destset.indexOf(stat.to) != -1) {
            table.append("
"+stat.line+"	"+stat.to+"	"+stat.time+"
");
        } else {
            table.append("
"+stat.line+"	"+stat.to+"	"+stat.time+"
");
        }
    });
}

function loadClock(){

    // Format: Samstag, 21. Januar 2017

    var displayDate = $('#date');
    var displayClock = $('#clock');

    var date = new Date();
    var year = date.getFullYear();
    var month = date.getMonth();
    var day = date.getDay();
    var hours = date.getHours();
    var minutes = date.getMinutes();

    hours < 10 ? hours = "0"+hours:null;
    minutes < 10 ? minutes = "0"+minutes:null;

    displayDate.text(days[day] +", "+day+". "+months[month]+" "+year);
    displayClock.text(hours+":"+minutes);
}

function loadDepartures(){
    $.ajax({
        url: "http://www.kvb-koeln.de/qr/"+params.depID,
        type: 'GET',
        success: function(res){
            getStations(res.responseText);
            $('#notif').text("");
        },
        error: function(err){
            $('#notif').text("Warning! Old data!");
        }
    });
}

function loadWeather(){
    var weather = $('#weather').weatherWidget({
        cacheTime: 1,
        lat: 50.941357,
        lon: 6.958307,
        key: params.forecastApi,
        celsius: true,
        imgPath:"./weather/img/"
    });
}

function loadCalendar(){
    var calendar = $('#calendar').fullCalendar({
        googleCalendarApiKey: params.calendarApi,
        locale: "de",
        timeFormat: "HH:mm",
        height: 620,
        eventSources: [
            {
                googleCalendarId: params.calenderaIDs[0]
            },
            {
                googleCalendarId: params.calenderaIDs[1]
            }
        ]
    });
}

function todayBackgroundColor(){
    var date = new Date().getDate();

    $('.fc-day-number').each(function(){
        if(this.innerHTML == date){
            $(this).addClass("today");
        }
    });
}

$(function(){
    todayBackgroundColor();

    loadClock();

    loadWeather();

    loadCalendar();

    loadDepartures();

    setInterval(function(){
        $('#calendar').fullCalendar('refetchEvents');
    }, 1000*60*60);

    setInterval(function(){
        $('#weather').html("");
        loadWeather();
    }, 1000*60*30);

    setInterval(function(){
        loadDepartures();
    }, 1000*10);

    setInterval(function(){
        loadClock();
    }, 1000*10);

});
<commit_msg>Clock<commit_after>/**
 * Smartmirror
 * ---
 * @author Michael Pomogajko
 * @version 0.1
 * ---
 * Note: Read the README!
 * ---
 */

var months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "November", "Dezember"];

var days = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];

function getStations(data){
    var buff = [];

    var str = data.substring(data.indexOf("<span class=\"stand"), data.indexOf("<div id=\"mobil_impressum"));

    $($(str)[2]).find('td').each(function(){
        buff.push(this);
    });

    var deps = [];

    for (var i = 0; i < 30; i++) {
        if(i%3 === 0){
            var dep = {};
            dep.line = buff[i].innerText;
            dep.to = buff[i+1].innerText;
            dep.time = buff[i+2].innerText;
            deps.push(dep);
        }
    }
    fillStations(deps);
}

function fillStations(stations){
    var table = $("#tbody");
    table.html("");

    var destset = ["Weiden West", "Junkersdorf", "Universität", "Sülz"];

    stations.forEach(function(stat){
        if(destset.indexOf(stat.to) != -1) {
            table.append("
"+stat.line+"	"+stat.to+"	"+stat.time+"
");
        } else {
            table.append("
"+stat.line+"	"+stat.to+"	"+stat.time+"
");
        }
    });
}

function loadClock(){

    // Format: Samstag, 21. Januar 2017

    var displayDate = $('#date');
    var displayClock = $('#clock');

    var date = new Date();
    var year = date.getFullYear();
    var month = date.getMonth();
    var weekday = date.getDay();
    var day = date.getDate();
    var hours = date.getHours();
    var minutes = date.getMinutes();

    console.log(day);

    hours < 10 ? hours = "0"+hours:null;
    minutes < 10 ? minutes = "0"+minutes:null;

    displayDate.text(days[weekday] +", "+day+". "+months[month]+" "+year);
    displayClock.text(hours+":"+minutes);
}

function loadDepartures(){
    $.ajax({
        url: "http://www.kvb-koeln.de/qr/"+params.depID,
        type: 'GET',
        success: function(res){
            getStations(res.responseText);
            $('#notif').text("");
        },
        error: function(err){
            $('#notif').text("Warning! Old data!");
        }
    });
}

function loadWeather(){
    var weather = $('#weather').weatherWidget({
        cacheTime: 1,
        lat: 50.941357,
        lon: 6.958307,
        key: params.forecastApi,
        celsius: true,
        imgPath:"./weather/img/"
    });
}

function loadCalendar(){
    var calendar = $('#calendar').fullCalendar({
        googleCalendarApiKey: params.calendarApi,
        locale: "de",
        timeFormat: "HH:mm",
        height: 620,
        eventSources: [
            {
                googleCalendarId: params.calenderaIDs[0]
            },
            {
                googleCalendarId: params.calenderaIDs[1]
            }
        ]
    });
}

function todayBackgroundColor(){
    var date = new Date().getDate();

    $('.fc-day-number').each(function(){
        if(this.innerHTML == date){
            $(this).addClass("today");
        }
    });
}

$(function(){
    todayBackgroundColor();

    loadClock();

    loadWeather();

    loadCalendar();

    loadDepartures();

    setInterval(function(){
        $('#calendar').fullCalendar('refetchEvents');
    }, 1000*60*60);

    setInterval(function(){
        $('#weather').html("");
        loadWeather();
    }, 1000*60*30);

    setInterval(function(){
        loadDepartures();
    }, 1000*10);

    setInterval(function(){
        loadClock();
    }, 1000*10);

});
<|endoftext|>


<commit_before>var renderer, stage, container, graphics, zoom,
            world, boxShape, boxBody, planeBody, planeShape,chassisBody,player, cars=[];

        var carTexture;

        var PLAYER=Math.pow(2,1),
            CAR=Math.pow(2,2);

        init();

        function Car(x,y,w,h,angle,velX,velY,mass,world,container,collisionGroup,stage){
            this.chassisBody = new p2.Body({
                position: [x,y],
                mass: mass,
                angle: angle,
                velocity: [velX, velY],
            });

            this.boxShape = new p2.Box({ width: w, height: h });
            this.boxShape.collisionGroup = collisionGroup;
            this.boxShape.collisionMask = PLAYER | CAR
            this.chassisBody.addShape(this.boxShape);
            world.addBody(this.chassisBody);
            // Create the vehicle
            this.vehicle = new p2.TopDownVehicle(this.chassisBody);
            // Add one front wheel and one back wheel - we don't actually need four :)

            this.chassisBody.frontWheel = this.vehicle.addWheel({
                localPosition: [0, 0.5] // front
            });

            this.chassisBody.frontWheel.setSideFriction(200);
            // Back wheel
            this.chassisBody.backWheel = this.vehicle.addWheel({
                localPosition: [0, -0.5] // back
            });
            this.chassisBody.backWheel.setSideFriction(200); // Less side friction on back wheel makes it easier to drift
            this.vehicle.addToWorld(world);

            this.graphics = new PIXI.Graphics();
            this.graphics.beginFill(0xff0000);
            this.texture = carTexture;
            this.sprite = new PIXI.Sprite(this.texture);
            this.graphics.addChild(this.sprite);
            this.graphics.drawRect(-this.boxShape.width/2, -this.boxShape.height/2, this.boxShape.width, this.boxShape.height);
            this.sprite.width = -this.boxShape.width;
            this.sprite.height = -this.boxShape.height;
            this.sprite.position={x:-this.boxShape.width/2, y:this.boxShape.height/2}
            this.sprite.scale.x = -this.sprite.scale.x
            // Add the box to our container
            container.addChild(this.graphics);

            this.update = function(){
                this.graphics.position.x = this.chassisBody.position[0];
                this.graphics.position.y = this.chassisBody.position[1];
                this.graphics.rotation =   this.chassisBody.angle;
            }
        }

        function init(){

            // Init p2.js
            // Create a dynamic body for the chassis
            world = new p2.World({
                gravity : [0,0]
            });

            zoom = 50;

            // Initialize the stage
            renderer =  PIXI.autoDetectRenderer(800, 600),
            stage = new PIXI.Stage(0xFFFFFF);
            container = new PIXI.Container(),
            PIXI.scaleModes.DEFAULT = PIXI.scaleModes.NEAREST;
            PIXI.loader
                        .add('car', 'assets/car.png')
                        .load(function (loader, resources) {
                            carTexture = resources.car.texture;
                            player = new Car(0,0,0.5,1,-1.5,15,0,2,world,container,PLAYER,stage);
                            animate();
            });
            stage.addChild(container);
            document.body.appendChild(renderer.view);
            // Add transform to the container
            container.position.x =  renderer.width/10; // center at origin
            container.position.y =  renderer.height/10;
            container.scale.x =  zoom;  // zoom in
            container.scale.y = -zoom; // Note: we flip the y axis to make "up" the physics "up"

            world.on("impact",function(evt){
                console.log(evt)
                var bodyA = evt.bodyA,
                bodyB = evt.bodyB;
                bodyA.frontWheel.setSideFriction(2);
                bodyB.frontWheel.setSideFriction(2);

                bodyA.backWheel.setSideFriction(2);
                bodyB.backWheel.setSideFriction(2);
            });0

            // Draw the box.

            var keys = {
                '37': 0, // left
                '39': 0, // right
                '38': 0, // up
                '40': 0 // down
            };
            var maxSteer = 20000;
            window.addEventListener("keydown",function (evt){
                keys[evt.keyCode] = 1;
                onInputChange();
            });
            window.addEventListener("keyup",function (evt){
                keys[evt.keyCode] = 0;
                onInputChange();
            });
            function onInputChange(){
                // Steer value zero means straight forward. Positive is left and negative right.
                player.chassisBody.frontWheel.steerValue = maxSteer * (keys[37] - keys[39]);
                player.chassisBody.backWheel.setBrakeForce(0);
                if(keys[40]){
                    if(player.chassisBody.backWheel.getSpeed() > 0.1){
                        // Moving forward - add some brake force to slow down
                        player.chassisBody.backWheel.setBrakeForce(2);
                    } else {
                        // Moving backwards - reverse the engine force
                        player.chassisBody.backWheel.setBrakeForce(2);
                    }
                }
            }
        }

        // Animation loop
        function animate(t){
            t = t || 0;
            requestAnimationFrame(animate);

            // Move physics bodies forward in time
            world.step(1/60);

            // Transfer positions of the physics objects to Pixi.js

            player.update();
            for (var i = cars.length - 1; i >= 0; i--) {
                cars[i].update()
            };
            // console.log(p2.vec2.length(player.chassisBody.velocity))
            if(p2.vec2.length(player.chassisBody.velocity) <= 0.05){
                player.chassisBody.backWheel.setBrakeForce(2);
                player.boxShape.collisionGroup = collisionGroup;
                cars.push(player);
                player = new Car(0,0,0.5,1,-1.5,15,0,2,world,container,PLAYER,stage);

            }
            // Render scene
            renderer.render(stage);
        }<commit_msg>variables r tricky<commit_after>var renderer, stage, container, graphics, zoom,
            world, boxShape, boxBody, planeBody, planeShape,chassisBody,player, cars=[];

        var carTexture;

        var PLAYER=Math.pow(2,1),
            CAR=Math.pow(2,2);

        init();

        function Car(x,y,w,h,angle,velX,velY,mass,world,container,collisionGroup,stage){
            this.chassisBody = new p2.Body({
                position: [x,y],
                mass: mass,
                angle: angle,
                velocity: [velX, velY],
            });

            this.boxShape = new p2.Box({ width: w, height: h });
            this.boxShape.collisionGroup = collisionGroup;
            this.boxShape.collisionMask = PLAYER | CAR
            this.chassisBody.addShape(this.boxShape);
            world.addBody(this.chassisBody);
            // Create the vehicle
            this.vehicle = new p2.TopDownVehicle(this.chassisBody);
            // Add one front wheel and one back wheel - we don't actually need four :)

            this.chassisBody.frontWheel = this.vehicle.addWheel({
                localPosition: [0, 0.5] // front
            });

            this.chassisBody.frontWheel.setSideFriction(200);
            // Back wheel
            this.chassisBody.backWheel = this.vehicle.addWheel({
                localPosition: [0, -0.5] // back
            });
            this.chassisBody.backWheel.setSideFriction(200); // Less side friction on back wheel makes it easier to drift
            this.vehicle.addToWorld(world);

            this.graphics = new PIXI.Graphics();
            this.graphics.beginFill(0xff0000);
            this.texture = carTexture;
            this.sprite = new PIXI.Sprite(this.texture);
            this.graphics.addChild(this.sprite);
            this.graphics.drawRect(-this.boxShape.width/2, -this.boxShape.height/2, this.boxShape.width, this.boxShape.height);
            this.sprite.width = -this.boxShape.width;
            this.sprite.height = -this.boxShape.height;
            this.sprite.position={x:-this.boxShape.width/2, y:this.boxShape.height/2}
            this.sprite.scale.x = -this.sprite.scale.x
            // Add the box to our container
            container.addChild(this.graphics);

            this.update = function(){
                this.graphics.position.x = this.chassisBody.position[0];
                this.graphics.position.y = this.chassisBody.position[1];
                this.graphics.rotation =   this.chassisBody.angle;
            }
        }

        function init(){

            // Init p2.js
            // Create a dynamic body for the chassis
            world = new p2.World({
                gravity : [0,0]
            });

            zoom = 50;

            // Initialize the stage
            renderer =  PIXI.autoDetectRenderer(800, 600),
            stage = new PIXI.Stage(0xFFFFFF);
            container = new PIXI.Container(),
            PIXI.scaleModes.DEFAULT = PIXI.scaleModes.NEAREST;
            PIXI.loader
                        .add('car', 'assets/car.png')
                        .load(function (loader, resources) {
                            carTexture = resources.car.texture;
                            player = new Car(0,0,0.5,1,-1.5,15,0,2,world,container,PLAYER,stage);
                            animate();
            });
            stage.addChild(container);
            document.body.appendChild(renderer.view);
            // Add transform to the container
            container.position.x =  renderer.width/10; // center at origin
            container.position.y =  renderer.height/10;
            container.scale.x =  zoom;  // zoom in
            container.scale.y = -zoom; // Note: we flip the y axis to make "up" the physics "up"

            world.on("impact",function(evt){
                console.log(evt)
                var bodyA = evt.bodyA,
                bodyB = evt.bodyB;
                bodyA.frontWheel.setSideFriction(2);
                bodyB.frontWheel.setSideFriction(2);

                bodyA.backWheel.setSideFriction(2);
                bodyB.backWheel.setSideFriction(2);
            });0

            // Draw the box.

            var keys = {
                '37': 0, // left
                '39': 0, // right
                '38': 0, // up
                '40': 0 // down
            };
            var maxSteer = 20000;
            window.addEventListener("keydown",function (evt){
                keys[evt.keyCode] = 1;
                onInputChange();
            });
            window.addEventListener("keyup",function (evt){
                keys[evt.keyCode] = 0;
                onInputChange();
            });
            function onInputChange(){
                // Steer value zero means straight forward. Positive is left and negative right.
                player.chassisBody.frontWheel.steerValue = maxSteer * (keys[37] - keys[39]);
                player.chassisBody.backWheel.setBrakeForce(0);
                if(keys[40]){
                    if(player.chassisBody.backWheel.getSpeed() > 0.1){
                        // Moving forward - add some brake force to slow down
                        player.chassisBody.backWheel.setBrakeForce(2);
                    } else {
                        // Moving backwards - reverse the engine force
                        player.chassisBody.backWheel.setBrakeForce(2);
                    }
                }
            }
        }

        // Animation loop
        function animate(t){
            t = t || 0;
            requestAnimationFrame(animate);

            // Move physics bodies forward in time
            world.step(1/60);

            // Transfer positions of the physics objects to Pixi.js

            player.update();
            for (var i = cars.length - 1; i >= 0; i--) {
                cars[i].update()
            };
            // console.log(p2.vec2.length(player.chassisBody.velocity))
            if(p2.vec2.length(player.chassisBody.velocity) <= 0.05){
                player.chassisBody.backWheel.setBrakeForce(2);
                player.boxShape.collisionGroup = CAR;
                cars.push(player);
                player = new Car(0,0,0.5,1,-1.5,15,0,2,world,container,PLAYER,stage);

            }
            // Render scene
            renderer.render(stage);
        }<|endoftext|>


<commit_before>/*
 * Websensor video stabilization project
 * https://github.com/jessenie-intel/websensor-video
 *
 * Copyright (c) 2017 Jesse Nieminen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

'use strict';

var constraints = {audio: true,video: {  width: { min: 640, ideal: 640, max: 640 },  height: { min: 480, ideal: 480, max: 480 }}};
var mediaRecorder = null;
var chunks = [];
var videoData = null;
var interval = null;

var accel_sensor = null;
var orientation_sensor = null;
var sensorfreq = 60;

//var roll = null;
//var pitch = null;
//var yaw = null;
var accel = {"x": null, "y": null, "z": null};
var ori = {"roll": null, "pitch": null, "yaw": null, "time": null};
var oriInitial = {"roll": null, "pitch": null, "yaw": null, "time": null};
var initialoriobtained = false;
var orientationData = [];       //array to store all the orientation data
var frameData = {"data": null, "time": null, "ori": null};
var dataArray = [];     //array to store all the combined data
var dataArray2 = [];     //array to store all the combined data

var time = null;
var timestamps = [];
var nFrame = 0; //frame number with which we can combine timestamp and frame data

//canvas
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

//video element
var videoElement = document.querySelector('video');
videoElement.controls = false;

class AbsOriSensor {
        constructor() {
        const sensor = new AbsoluteOrientationSensor({ frequency: sensorfreq });
        const mat4 = new Float32Array(16);
        const euler = new Float32Array(3);
        sensor.onreading = () => {
                sensor.populateMatrix(mat4);
                toEulerianAngle(sensor.quaternion, euler);      //From quaternion to Eulerian angles
                this.roll = euler[0];
                this.pitch = euler[1];
                this.yaw = euler[2];
                this.timestamp = sensor.timestamp;
                if (this.onreading) this.onreading();
        };
        sensor.onactivate = () => {
                if (this.onactivate) this.onactivate();
        };
        const start = () => sensor.start();
        Object.assign(this, { start });
        }
}

//WINDOWS 10 HAS DIFFERENT CONVENTION: Yaw z, pitch x, roll y
function toEulerianAngle(quat, out)
{
        const ysqr = quat[1] ** 2;

        // Roll (x-axis rotation).
        const t0 = 2 * (quat[3] * quat[0] + quat[1] * quat[2]);
        const t1 = 1 - 2 * (ysqr + quat[0] ** 2);
        out[0] = Math.atan2(t0, t1);
        // Pitch (y-axis rotation).
        let t2 = 2 * (quat[3] * quat[1] - quat[2] * quat[0]);
        t2 = t2 > 1 ? 1 : t2;
        t2 = t2 < -1 ? -1 : t2;
        out[1] = Math.asin(t2);
        // Yaw (z-axis rotation).
        const t3 = 2 * (quat[3] * quat[2] + quat[0] * quat[1]);
        const t4 = 1 - 2 * (ysqr + quat[2] ** 2);
        out[2] = Math.atan2(t3, t4);
        return out;
}

function errorCallback(error){
	console.log("error: ", error);
}

function startDemo () {
		navigator.getUserMedia(constraints, startRecording, errorCallback);
}

function startRecording(stream) {

                try {
                //Initialize sensors
                /*accel_sensor = new Accelerometer();
                accel_sensor.onreading = () => {
                        accel = {"x": accel_sensor.x, "y": accel_sensor.y, "z": accel_sensor.z};
                };
                accel_sensor.onactivate = () => {
                };
                accel_sensor.start();*/
                orientation_sensor = new AbsOriSensor();
                orientation_sensor.onreading = () => {
                        let roll = orientation_sensor.roll;
                        let pitch = orientation_sensor.pitch;
                        let yaw = orientation_sensor.yaw;
                        time = orientation_sensor.timestamp;
                        if(!initialoriobtained) //obtain initial orientation
                        {
                                oriInitial = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                                initialoriobtained = true;
                        }
                        ori = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                };
                orientation_sensor.onactivate = () => {
                };
                orientation_sensor.start();
                }
                catch(err) {
                        console.log(err.message);
                        console.log("Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags.");
                        //this.innerHTML = "Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags";
                }
	        //var options = {mimeType: 'video/webm;codecs=vp9'};
		//mediaRecorder = new MediaRecorder(stream, options);
		mediaRecorder = new MediaRecorder(stream);

	        mediaRecorder.start(10);

	        var url = window.URL;
	        videoElement.src = url ? url.createObjectURL(stream) : stream;
	        videoElement.play();

	        mediaRecorder.ondataavailable = function(e) {
                        //console.log("Data available", e);
                        //console.log(time);
                        timestamps.push(time);
                        frameData.time = time;
		        chunks.push(e.data);
                        frameData.data = e.data;
                        orientationData.push(ori);
                        frameData.ori = ori;
                        var b = new Object;     //need to push by value
                        Object.assign(b, frameData);
                        dataArray.push(b);
	        };

	        mediaRecorder.onerror = function(e){
		        console.log('Error: ', e);
	        };

	        mediaRecorder.onstart = function(){
                        console.log("Recording started", mediaRecorder.state);
	        };

	        mediaRecorder.onstop = function(){
		        var blob = new Blob(chunks, {type: "video/webm"});
		        chunks = [];

		        var videoURL = window.URL.createObjectURL(blob);

		        videoElement.src = videoURL;

                        //resize canvas
videoElement.addEventListener('loadedmetadata', function() {
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
});

                        /*//Read blob data so we can stabilize the video
                        var reader = new FileReader();
                         reader.onload = function(event){
                                let text = reader.result;
                                console.log(text);
                          };
                        reader.readAsText(blob, "video/webm");*/
                        //console.log(orientationData);
                        //console.log(timestamps);
                        console.log(dataArray);
/*
var blobUrl = URL.createObjectURL(blob);
var x = new XMLHttpRequest();
// set x.responseType = 'blob' if you want to get a Blob object:
// x.responseType = 'blob';
x.onload = function() {
    alert(x.responseText);
};
console.log(x.open('get', blobUrl));*/
                        stabilize();
                        //interval=window.setInterval(stabilize,20);
	        };

	        mediaRecorder.onpause = function(){
	        }

	        mediaRecorder.onresume = function(){
	        }

	        mediaRecorder.onwarning = function(e){
	        };
}

function stopRecording(){
	mediaRecorder.stop();
        //Now stabilize
        //stabilize(blob);
	videoElement.controls = true;
}

function stabilize() {     //Idea: copy video to canvas, operate on the video, and then use the canvas with the stabilized video as source for the video element
        //console.log(orientationData);
        //console.log(oriInitial);
        //console.log(timestamps[nFrame] - timestamps[0], videoElement.currentTime);
        let x = 0;
        let y = 0;
        let width = 100;
        let height = 100;
        let oriDiff = {"roll": -oriInitial.roll, "pitch": -oriInitial.pitch, "yaw": -oriInitial.yaw};
        ctx.drawImage(videoElement,0,0, videoElement.videoWidth, videoElement.videoHeight);
        ctx.beginPath();
        ctx.rect(x,y,width,height);
        ctx.stroke();

        //ctx.drawImage(videoElement, 0, 0);
        let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        let pixeldataArray = [];
        //loop through every pixel
        for(let i=0; i<imageData.data.length; i = i+4)
        {
                let pixeldata = {"red": imageData.data[i], "green":imageData.data[i+1], "blue": imageData.data[i+2], "alpha":imageData.data[i+3]};
                pixeldataArray.push(pixeldata);
        }
        let timestamp = dataArray[nFrame].time;
        let frameData2 = {"pixeldata": pixeldataArray, "time": timestamp};
        dataArray2.push(frameData2);
        //console.log(pixeldataArray);
        //newImageData.data = data;
    // Draw the pixels onto the visible canvas
    //ctx.putImageData(newImageData,0,0);
        //xD
        x = x + oriDiff.roll;
        y = y + oriDiff.pitch;
        nFrame = nFrame + 1;
        requestAnimationFrame(stabilize);
}
<commit_msg>Combine sensor data, video data and timestamp<commit_after>/*
 * Websensor video stabilization project
 * https://github.com/jessenie-intel/websensor-video
 *
 * Copyright (c) 2017 Jesse Nieminen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

'use strict';

var constraints = {audio: true,video: {  width: { min: 640, ideal: 640, max: 640 },  height: { min: 480, ideal: 480, max: 480 }}};
var mediaRecorder = null;
var chunks = [];
var videoData = null;
var interval = null;

var accel_sensor = null;
var orientation_sensor = null;
var sensorfreq = 60;

//var roll = null;
//var pitch = null;
//var yaw = null;
var accel = {"x": null, "y": null, "z": null};
var ori = {"roll": null, "pitch": null, "yaw": null, "time": null};
var oriInitial = {"roll": null, "pitch": null, "yaw": null, "time": null};
var initialoriobtained = false;
var orientationData = [];       //array to store all the orientation data
var frameData = {"data": null, "time": null, "ori": null};
var dataArray = [];     //array to store all the combined data
var dataArray2 = [];     //array to store all the combined data

var time = null;
var timestamps = [];
var nFrame = 0; //frame number with which we can combine timestamp and frame data

//canvas
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

//video element
var videoElement = document.querySelector('video');
videoElement.controls = false;

class AbsOriSensor {
        constructor() {
        const sensor = new AbsoluteOrientationSensor({ frequency: sensorfreq });
        const mat4 = new Float32Array(16);
        const euler = new Float32Array(3);
        sensor.onreading = () => {
                sensor.populateMatrix(mat4);
                toEulerianAngle(sensor.quaternion, euler);      //From quaternion to Eulerian angles
                this.roll = euler[0];
                this.pitch = euler[1];
                this.yaw = euler[2];
                this.timestamp = sensor.timestamp;
                if (this.onreading) this.onreading();
        };
        sensor.onactivate = () => {
                if (this.onactivate) this.onactivate();
        };
        const start = () => sensor.start();
        Object.assign(this, { start });
        }
}

//WINDOWS 10 HAS DIFFERENT CONVENTION: Yaw z, pitch x, roll y
function toEulerianAngle(quat, out)
{
        const ysqr = quat[1] ** 2;

        // Roll (x-axis rotation).
        const t0 = 2 * (quat[3] * quat[0] + quat[1] * quat[2]);
        const t1 = 1 - 2 * (ysqr + quat[0] ** 2);
        out[0] = Math.atan2(t0, t1);
        // Pitch (y-axis rotation).
        let t2 = 2 * (quat[3] * quat[1] - quat[2] * quat[0]);
        t2 = t2 > 1 ? 1 : t2;
        t2 = t2 < -1 ? -1 : t2;
        out[1] = Math.asin(t2);
        // Yaw (z-axis rotation).
        const t3 = 2 * (quat[3] * quat[2] + quat[0] * quat[1]);
        const t4 = 1 - 2 * (ysqr + quat[2] ** 2);
        out[2] = Math.atan2(t3, t4);
        return out;
}

function errorCallback(error){
	console.log("error: ", error);
}

function startDemo () {
		navigator.getUserMedia(constraints, startRecording, errorCallback);
}

function startRecording(stream) {

                try {
                //Initialize sensors
                /*accel_sensor = new Accelerometer();
                accel_sensor.onreading = () => {
                        accel = {"x": accel_sensor.x, "y": accel_sensor.y, "z": accel_sensor.z};
                };
                accel_sensor.onactivate = () => {
                };
                accel_sensor.start();*/
                orientation_sensor = new AbsOriSensor();
                orientation_sensor.onreading = () => {
                        let roll = orientation_sensor.roll;
                        let pitch = orientation_sensor.pitch;
                        let yaw = orientation_sensor.yaw;
                        time = orientation_sensor.timestamp;
                        if(!initialoriobtained) //obtain initial orientation
                        {
                                oriInitial = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                                initialoriobtained = true;
                        }
                        ori = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                };
                orientation_sensor.onactivate = () => {
                };
                orientation_sensor.start();
                }
                catch(err) {
                        console.log(err.message);
                        console.log("Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags.");
                        //this.innerHTML = "Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags";
                }
	        //var options = {mimeType: 'video/webm;codecs=vp9'};
		//mediaRecorder = new MediaRecorder(stream, options);
		mediaRecorder = new MediaRecorder(stream);

	        mediaRecorder.start(10);

	        var url = window.URL;
	        videoElement.src = url ? url.createObjectURL(stream) : stream;
	        videoElement.play();

	        mediaRecorder.ondataavailable = function(e) {
                        //console.log("Data available", e);
                        //console.log(time);
                        timestamps.push(time);
                        frameData.time = time;
		        chunks.push(e.data);
                        frameData.data = e.data;
                        orientationData.push(ori);
                        frameData.ori = ori;
                        var b = new Object;     //need to push by value
                        Object.assign(b, frameData);
                        dataArray.push(b);
	        };

	        mediaRecorder.onerror = function(e){
		        console.log('Error: ', e);
	        };

	        mediaRecorder.onstart = function(){
                        console.log("Recording started", mediaRecorder.state);
	        };

	        mediaRecorder.onstop = function(){
		        var blob = new Blob(chunks, {type: "video/webm"});
		        chunks = [];

		        var videoURL = window.URL.createObjectURL(blob);

		        videoElement.src = videoURL;

                        //resize canvas
videoElement.addEventListener('loadedmetadata', function() {
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
});

                        /*//Read blob data so we can stabilize the video
                        var reader = new FileReader();
                         reader.onload = function(event){
                                let text = reader.result;
                                console.log(text);
                          };
                        reader.readAsText(blob, "video/webm");*/
                        //console.log(orientationData);
                        //console.log(timestamps);
                        //console.log(dataArray);
/*
var blobUrl = URL.createObjectURL(blob);
var x = new XMLHttpRequest();
// set x.responseType = 'blob' if you want to get a Blob object:
// x.responseType = 'blob';
x.onload = function() {
    alert(x.responseText);
};
console.log(x.open('get', blobUrl));*/
                        stabilize();
                        console.log(dataArray2);
                        //interval=window.setInterval(stabilize,20);
	        };

	        mediaRecorder.onpause = function(){
	        }

	        mediaRecorder.onresume = function(){
	        }

	        mediaRecorder.onwarning = function(e){
	        };
}

function stopRecording(){
	mediaRecorder.stop();
        //Now stabilize
        //stabilize(blob);
	videoElement.controls = true;
}

function stabilize() {     //Idea: copy video to canvas, operate on the video, and then use the canvas with the stabilized video as source for the video element
        //console.log(orientationData);
        //console.log(oriInitial);
        //console.log(timestamps[nFrame] - timestamps[0], videoElement.currentTime);
        let x = 0;
        let y = 0;
        let width = 100;
        let height = 100;
        let oriDiff = {"roll": -oriInitial.roll, "pitch": -oriInitial.pitch, "yaw": -oriInitial.yaw};
        ctx.drawImage(videoElement,0,0, videoElement.videoWidth, videoElement.videoHeight);
        ctx.beginPath();
        ctx.rect(x,y,width,height);
        ctx.stroke();

        //ctx.drawImage(videoElement, 0, 0);
        let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        let pixeldataArray = [];
        //loop through every pixel
        for(let i=0; i<imageData.data.length; i = i+4)
        {
                let pixeldata = {"red": imageData.data[i], "green":imageData.data[i+1], "blue": imageData.data[i+2], "alpha":imageData.data[i+3]};
                pixeldataArray.push(pixeldata);
        }
        let timestamp = dataArray[nFrame].time;
        let frameData2 = {"pixeldata": pixeldataArray, "time": timestamp};
        dataArray2.push(frameData2);
        //console.log(pixeldataArray);
        //newImageData.data = data;
    // Draw the pixels onto the visible canvas
    //ctx.putImageData(newImageData,0,0);
        //xD
        x = x + oriDiff.roll;
        y = y + oriDiff.pitch;
        nFrame = nFrame + 1;
        requestAnimationFrame(stabilize);
}
<|endoftext|>


<commit_before>var dataRef = new Firebase("https://sudarshan.firebaseio.com/");
var chatRef = new Firebase("https://sudarshan.firebaseio.com/**chats**");
var id = Math.random().toString(16).slice(2);

filepicker.setKey("AWgdx42rMTr6jvgv3Vwl4z");

var ytplayer = null;
var mp4player = null;

var roomName = null;
var tokboxSession = null;
var tokboxToken = null;

var disableUntil;

var last_in = null;

function makeRoom (name) {
  $.getJSON("http://www.smuralidhar.com/pennapps2014s/generate.php", function(res){
      dataRef = dataRef.root();
      dataRef = dataRef.child(name);
      dataRef.on('value', incoming);
      dataRef.update(
      {
        type: null,
        video: null,
        seek: 0,
        timestamp: now(),
        playing: false,
        sessionId: res.sessionId,
        token: res.token,
        owner: id,
        chat: []
      }
    );
    chatRef = chatRef.child(name);
    chatRef.on('child_added', chatIn)
  });
}

function joinRoom (name) {
  dataRef = dataRef.root();
  dataRef = dataRef.child(name);
  dataRef.on('value', incoming);
  chatRef = chatRef.child(name);
  chatRef.on('child_added', chatIn)
}

function chatIn (fb) {
  var data = fb.val();
  var date = new Date(data.timestamp).toLocaleTimeString()
  var me = data.id == id ? " class='self'" : "";
  var html = "
" +
                "" + data.name + " " +
                "" +
                date.substring(0, date.length - 6) + " " +
                "" + data.message + "" +
            "
";
  $("#chat ul").append(html);
}

function chatOut (msg) {
  var nickname = $("#nickname")[0].value;
  chatRef.push({
    id: id,
    name: (nickname ? nickname : id),
    message: msg,
    timestamp: new Date().getTime()
  })
}

function incoming (fb) {
  var data = fb.val();
  last_in = data;
  console.log("inc");
  console.log(data);

  if (data == null){
    alert("room does not exist");
  }

  if (roomName == null){
    roomName = name;
    tokboxSession = data.sessionId;
    tokboxToken = data.token;
    showWelcomeMessage();
    $("#chat").show();

    runWebcam(tokboxSession, tokboxToken);
  }

  if (data.type == "YT") {
    if (ytplayer == null)
      makeYtPlayer(data.video);
    else
      handleYtInc(data);
  }
  else if (data.type == "mp4"){
    if (mp4player == null)
      makeMp4Player(data);
    else
      handleMp4Inc(data);
  }
}

function outgoing (playing, seek) {
  console.log("out");
  console.log({playing:playing, seek:seek});
  dataRef.update(
    {playing: playing, seek: seek, timestamp: now(), owner: id}
  );
}

function showWelcomeMessage () {
  $("#inputs").html("
	" +
    "<input id='vid' type='text' placeholder='Choose a video (Youtube or MP4)'></input>	" +
    "<button id='choose'>Load</button><button id='upload'>Upload</button>
");
  $("#choose").click(function(){
    var vid = $("#vid")[0].value + "#";
    if (vid.indexOf("youtube.com")>-1){
      var key = vid.substring(vid.indexOf("v=")+2, vid.indexOf("#"));
      var type = "YT";
      dataRef.update({type: type, video: key});
    }
    else if (vid.indexOf(".mp4") > -1){
      dataRef.update({type:"mp4", video: $("#vid")[0].value});
    }
    else
      alert ("Link not recognized");
  });
  $("#upload").click(function(){
    filepicker.pick(
      {extension: '.mp4'},
      function(res){
        dataRef.update({type: "mp4", video: res.url});
      });
  });
}

function makeYtPlayer(vid) {
  disableUntil = -1;
  var params = { allowScriptAccess: "always" };
  var atts = { id: "myytplayer" };
  swfobject.embedSWF("http://www.youtube.com/v/" + vid + "?version=3&controls=1&enablejsapi=1&playerapiid=ytplayer&allowfullscreen=1",
    "ytapiplayer", "100%", "100%", "8", null, null, params, atts);
}
function makeMp4Player(data) {
  disableUntil = false;
  jwplayer("ytapiplayer").setup({file: data.video, width: "80%", height: "90%"});
  mp4player = jwplayer();
  mp4player.onReady(function() {
    console.log('setup');

    mp4player.onSeek(mp4SeekListener);
    mp4player.onPlay(function() {mp4PlayPauseListener(true)});
    mp4player.onPause(function() {mp4PlayPauseListener(false)});

    handleMp4Inc(data);

    setInterval(function(){
      dataRef.once('value', function(data){
        var pres = now();
        var curr = mp4player.getPosition();
        data = data.val();
        if (data.playing)
          loc = data.seek + pres - data.timestamp;
        else
          loc = data.seek;
        var playing = mp4player.getState() == "PLAYING";

        console.log(curr - loc);
        if (Math.abs (curr - loc) > 2 ||
            data.playing != playing){
          if (data.owner == id)
            outgoing(playing, mp4player.getPosition());
          else
            handleMp4Inc(data);
      }
      })
    }, 2000);
  })
}

function onYouTubePlayerReady(playerId) {
  ytplayer = $("#myytplayer")[0];
  ytplayer.addEventListener("onStateChange", "ytListener");

  handleYtInc(last_in);

  setInterval(function(){
    dataRef.once('value', function(data){
      var pres = now();
      var curr = ytplayer.getCurrentTime();
      data = data.val();
      if (data.playing)
        loc = data.seek + pres - data.timestamp;
      else
        loc = data.seek;
      var playing = ytplayer.getPlayerState() == 1;

      //console.log(curr - loc);
      if (Math.abs (curr - loc) > 0.5 ||
          data.playing != playing)
        handleYtInc(data);
    })
  }, 2000);
}

function handleYtInc(data) {
  // disable the listener until state is correct
  if (data.playing)
    disableUntil = 1;
  else
    disableUntil = 2;

  if (data.playing)
    data.seek += now() - data.timestamp;

  ytplayer.seekTo(data.seek);

  if (data.playing)
    ytplayer.playVideo();
  else
    ytplayer.pauseVideo();
}

function handleMp4Inc(data) {
  if(data.owner == id){
    console.log("ignoring own")
    return;
  }
  if (data.playing)
    data.seek += now() - data.timestamp;

  // ignore 2 events (assume it take under .05 seconds)
  disableUntil = true;
  console.log("disabling handlers");
  setTimeout(function(){
    disableUntil = false;
    console.log("TIME UP!")
  }, 50)
  mp4player.seek(data.seek);
  mp4player.play(data.playing);

  console.log('done handle');
}

function ytListener (value){
  if (disableUntil > -1){
    console.log("ignoring- " + value);
    if (disableUntil == value)
      disableUntil = -1;
    return;
  }
  switch (value) {
    case -1:
      console.log("unstarted");
      disableUntil = 2;
      ytplayer.seekTo(0);
      ytplayer.pauseVideo();
      break;
    case 0:
      console.log("ended");
      break;
    case 1:
      console.log("playing");
      disableUntil = 1;
      ytplayer.pauseVideo();
      outgoing (true, ytplayer.getCurrentTime());
      break;
    case 2:
      console.log("paused");
      disableUntil = 2;
      ytplayer.playVideo();
      outgoing (false, ytplayer.getCurrentTime());
      break;
    case 3:
      console.log("buffering");
      break;
    case 5:
      console.log("cued");
      break;
  }
}

function mp4PlayPauseListener (play){
  if (disableUntil){
    console.log("ignoring " + play);
    return;
  }

  console.log(play);

  //mp4player.pause(play);
  outgoing (play, mp4player.getPosition());
}
function mp4SeekListener () {
  if (disableUntil){
    console.log("ignoring seek");
    return;
  }
  console.log("seek");
  outgoing (false, mp4player.getPosition(), function(){
    mp4player.pause();
  });
}

function now () {
  return new Date().getTime() / 1000;
}

$(document).ready (function() {
  $("#create").click(function() {
    makeRoom($("#room")[0].value)
  });
  $("#join").click(function() {
    joinRoom($("#room")[0].value)
  });

  $("#message").keyup(function(e){
    e.preventDefault();
    var key = window.event ? e.keyCode : e.which;
    if (key == '13') {
        chatOut($("#message")[0].value);
	$("#message")[0].value = null;
    }
  })
})
<commit_msg>filepicker<commit_after>var dataRef = new Firebase("https://sudarshan.firebaseio.com/");
var chatRef = new Firebase("https://sudarshan.firebaseio.com/**chats**");
var id = Math.random().toString(16).slice(2);

filepicker.setKey("AWgdx42rMTr6jvgv3Vwl4z");

var ytplayer = null;
var mp4player = null;

var roomName = null;
var tokboxSession = null;
var tokboxToken = null;

var disableUntil;

var last_in = null;

function makeRoom (name) {
  $.getJSON("http://www.smuralidhar.com/pennapps2014s/generate.php", function(res){
      dataRef = dataRef.root();
      dataRef = dataRef.child(name);
      dataRef.on('value', incoming);
      dataRef.update(
      {
        type: null,
        video: null,
        seek: 0,
        timestamp: now(),
        playing: false,
        sessionId: res.sessionId,
        token: res.token,
        owner: id
      }
    );
    chatRef = chatRef.child(name);
    chatRef.on('child_added', chatIn)
  });
}

function joinRoom (name) {
  dataRef = dataRef.root();
  dataRef = dataRef.child(name);
  dataRef.on('value', incoming);
  chatRef = chatRef.child(name);
  chatRef.on('child_added', chatIn)
}

function chatIn (fb) {
  var data = fb.val();
  var date = new Date(data.timestamp).toLocaleTimeString()
  var me = data.id == id ? " class='self'" : "";
  var html = "
" +
                "" + data.name + " " +
                "" +
                date.substring(0, date.length - 6) + " " +
                "" + data.message + "" +
            "
";
  $("#chat ul").append(html);
}

function chatOut (msg) {
  var nickname = $("#nickname")[0].value;
  chatRef.push({
    id: id,
    name: (nickname ? nickname : id),
    message: msg,
    timestamp: new Date().getTime()
  })
}

function incoming (fb) {
  var data = fb.val();
  last_in = data;
  console.log("inc");
  console.log(data);

  if (data == null){
    alert("room does not exist");
  }

  if (roomName == null){
    roomName = name;
    tokboxSession = data.sessionId;
    tokboxToken = data.token;
    showWelcomeMessage();
    $("#chat").show();

    runWebcam(tokboxSession, tokboxToken);
  }

  if (data.type == "YT") {
    if (ytplayer == null)
      makeYtPlayer(data.video);
    else
      handleYtInc(data);
  }
  else if (data.type == "mp4"){
    if (mp4player == null)
      makeMp4Player(data);
    else
      handleMp4Inc(data);
  }
}

function outgoing (playing, seek) {
  console.log("out");
  console.log({playing:playing, seek:seek});
  dataRef.update(
    {playing: playing, seek: seek, timestamp: now(), owner: id}
  );
}

function showWelcomeMessage () {
  $("#inputs").html("
	" +
    "<input id='vid' type='text' placeholder='Choose a video (Youtube or MP4)'></input>	" +
    "<button id='choose'>Load</button><button id='upload'>Upload</button>
");
  $("#choose").click(function(){
    var vid = $("#vid")[0].value + "#";
    if (vid.indexOf("youtube.com")>-1){
      var key = vid.substring(vid.indexOf("v=")+2, vid.indexOf("#"));
      var type = "YT";
      dataRef.update({type: type, video: key});
    }
    else if (vid.indexOf(".mp4") > -1){
      dataRef.update({type:"mp4", video: $("#vid")[0].value});
    }
    else
      alert ("Link not recognized");
  });
  $("#upload").click(function(){
    filepicker.pick(
      {extension: '.mp4'},
      function(res){
        dataRef.update({type: "mp4", video: res.url});
      });
  });
}

function makeYtPlayer(vid) {
  disableUntil = -1;
  var params = { allowScriptAccess: "always" };
  var atts = { id: "myytplayer" };
  swfobject.embedSWF("http://www.youtube.com/v/" + vid + "?version=3&controls=1&enablejsapi=1&playerapiid=ytplayer&allowfullscreen=1",
    "ytapiplayer", "100%", "100%", "8", null, null, params, atts);
}
function makeMp4Player(data) {
  disableUntil = false;
  jwplayer("ytapiplayer").setup({file: data.video, width: "80%", height: "90%"});
  mp4player = jwplayer();
  mp4player.onReady(function() {
    console.log('setup');

    mp4player.onSeek(mp4SeekListener);
    mp4player.onPlay(function() {mp4PlayPauseListener(true)});
    mp4player.onPause(function() {mp4PlayPauseListener(false)});

    handleMp4Inc(data);

    setInterval(function(){
      dataRef.once('value', function(data){
        var pres = now();
        var curr = mp4player.getPosition();
        data = data.val();
        if (data.playing)
          loc = data.seek + pres - data.timestamp;
        else
          loc = data.seek;
        var playing = mp4player.getState() == "PLAYING";

        console.log(curr - loc);
        if (Math.abs (curr - loc) > 2 ||
            data.playing != playing){
          if (data.owner == id)
            outgoing(playing, mp4player.getPosition());
          else
            handleMp4Inc(data);
      }
      })
    }, 2000);
  })
}

function onYouTubePlayerReady(playerId) {
  ytplayer = $("#myytplayer")[0];
  ytplayer.addEventListener("onStateChange", "ytListener");

  handleYtInc(last_in);

  setInterval(function(){
    dataRef.once('value', function(data){
      var pres = now();
      var curr = ytplayer.getCurrentTime();
      data = data.val();
      if (data.playing)
        loc = data.seek + pres - data.timestamp;
      else
        loc = data.seek;
      var playing = ytplayer.getPlayerState() == 1;

      //console.log(curr - loc);
      if (Math.abs (curr - loc) > 0.5 ||
          data.playing != playing)
        handleYtInc(data);
    })
  }, 2000);
}

function handleYtInc(data) {
  // disable the listener until state is correct
  if (data.playing)
    disableUntil = 1;
  else
    disableUntil = 2;

  if (data.playing)
    data.seek += now() - data.timestamp;

  ytplayer.seekTo(data.seek);

  if (data.playing)
    ytplayer.playVideo();
  else
    ytplayer.pauseVideo();
}

function handleMp4Inc(data) {
  if(data.owner == id){
    console.log("ignoring own")
    return;
  }
  if (data.playing)
    data.seek += now() - data.timestamp;

  // ignore 2 events (assume it take under .05 seconds)
  disableUntil = true;
  console.log("disabling handlers");
  setTimeout(function(){
    disableUntil = false;
    console.log("TIME UP!")
  }, 50)
  mp4player.seek(data.seek);
  mp4player.play(data.playing);

  console.log('done handle');
}

function ytListener (value){
  if (disableUntil > -1){
    console.log("ignoring- " + value);
    if (disableUntil == value)
      disableUntil = -1;
    return;
  }
  switch (value) {
    case -1:
      console.log("unstarted");
      disableUntil = 2;
      ytplayer.seekTo(0);
      ytplayer.pauseVideo();
      break;
    case 0:
      console.log("ended");
      break;
    case 1:
      console.log("playing");
      disableUntil = 1;
      ytplayer.pauseVideo();
      outgoing (true, ytplayer.getCurrentTime());
      break;
    case 2:
      console.log("paused");
      disableUntil = 2;
      ytplayer.playVideo();
      outgoing (false, ytplayer.getCurrentTime());
      break;
    case 3:
      console.log("buffering");
      break;
    case 5:
      console.log("cued");
      break;
  }
}

function mp4PlayPauseListener (play){
  if (disableUntil){
    console.log("ignoring " + play);
    return;
  }

  console.log(play);

  //mp4player.pause(play);
  outgoing (play, mp4player.getPosition());
}
function mp4SeekListener () {
  if (disableUntil){
    console.log("ignoring seek");
    return;
  }
  console.log("seek");
  outgoing (false, mp4player.getPosition(), function(){
    mp4player.pause();
  });
}

function now () {
  return new Date().getTime() / 1000;
}

$(document).ready (function() {
  $("#create").click(function() {
    makeRoom($("#room")[0].value)
  });
  $("#join").click(function() {
    joinRoom($("#room")[0].value)
  });

  $("#message").keyup(function(e){
    e.preventDefault();
    var key = window.event ? e.keyCode : e.which;
    if (key == '13') {
        chatOut($("#message")[0].value);
	$("#message")[0].value = null;
    }
  })
})
<|endoftext|>