Permalink
Browse files

Start on #57, recording and playing back sessions, using /record and …

…/playback from the chat box
  • Loading branch information...
1 parent ab8295b commit 218b5825ecd9e90a3e6f7e752cef11fb0a0f1f06 @ianb ianb committed Mar 11, 2013
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>TowTruck recorder</title>
+ <script>
+ // FIXME: need to make this dynamic:
+ defaultHubBase = "";
+ baseUrl = (location.href + "").replace(/\/recorder\.html.*/, "");
+ // FIXME: this is copied from towtruck.js:
+ require = {
+ context: "towtruck",
+ baseUrl: baseUrl + "/towtruck",
+ urlArgs: "bust=" + Date.now(),
+ paths: {
+ jquery: "libs/jquery-1.8.3.min",
+ walkabout: "libs/walkabout.js/walkabout",
+ tinycolor: "libs/tinycolor",
+ "alien-avatar-generator": "libs/alien-avatar-generator"
+ },
+ deps: ["recorder"],
+ callback: function () {
+ require({context: "towtruck"})(["recorder"], function (recorder) {
+ recorder.start({
+ baseUrl: baseUrl,
+ defaultHubBase: defaultHubBase
+ });
+ });
+ }
+ };
+ console.log("require", require);
+ </script>
+ <script src="./towtruck/libs/require.js"></script>
+ <link rel="stylesheet" href="recorder.css">
+ </head>
+ <body>
+
+<div id="connection-status">
+ <span id="not-connected" data-toggles="#connection-status span">Not Connected</span>
+ <span id="connected" data-toggles="#connection-status span" style="display: none">Connected</span>
+ <span id="no-session-id" data-toggles="#connection-status span" style="display: none">There's no <code>#&amp;towtruck=SESSION_ID</code> in the URL.</span>
+ <button id="restart">Restart recording</button>
+ <button id="select">Select record</button>
+</div>
+
+<div>
+ <textarea id="record">/* TowTruck session starts */
+
+</textarea>
+</div>
+
+
+ </body>
+</html>
@@ -0,0 +1,17 @@
+body {
+ font-family: sans-serif;
+}
+
+#record {
+ width: 100%;
+ height: 80%;
+}
+
+#connected {
+ font-weight: bold;
+ color: #090;
+}
+
+#not-connected {
+ color: #900;
+}
@@ -1,4 +1,4 @@
-define(["require", "jquery", "util", "session", "ui", "templates"], function (require, $, util, session, ui, templates) {
+define(["require", "jquery", "util", "session", "ui", "templates", "playback"], function (require, $, util, session, ui, templates, playback) {
var chat = util.Module("chat");
var assert = util.assert;
@@ -230,6 +230,54 @@ define(["require", "jquery", "util", "session", "ui", "templates"], function (re
}
},
+ command_record: function () {
+ ui.addChat({
+ type: "system",
+ text: "When you see the robot appear, the recording will have started"
+ });
+ window.open(
+ session.recordUrl(), "_blank",
+ "left,width=" + ($(window).width() / 2));
+ },
+
+ playing: null,
+
+ command_playback: function (url) {
+ if (this.playing) {
+ this.playing.cancel();
+ this.playing.unload();
+ this.playing = null;
+ ui.addChat({
+ type: "system",
+ text: "playback cancelled"
+ });
+ return;
+ }
+ if (! url) {
+ ui.addChat({
+ type: "system",
+ text: "Nothing is playing"
+ });
+ return;
+ }
+ var logLoader = playback.getLogs(url);
+ logLoader.then(
+ (function (logs) {
+ logs.save();
+ this.playing = logs;
+ logs.play();
+ }).bind(this),
+ function (error) {
+ ui.addChat({
+ type: "system",
+ text: "Error fetching " + url + ":\n" + JSON.stringify(error, null, " ")
+ });
+ });
+ // FIXME: I shouldn't be doing this directly here, I should
+ // call some ui.* function:
+ $("#towtruck-chat").hide();
+ },
+
storageKey: "towtruck.chatlog",
messageExpireTime: 1000 * 60 * 60 * 6, // 6 hours in milliseconds
maxLogMessages: 100,
@@ -292,6 +340,12 @@ define(["require", "jquery", "util", "session", "ui", "templates"], function (re
messageId: l.messageId
});
}
+ // Re-load a partial playback:
+ var logs = playback.getRunningLogs();
+ if (logs) {
+ chat.Chat.playing = logs;
+ logs.play();
+ }
});
return chat;
@@ -0,0 +1,174 @@
+define(["jquery", "util", "session"], function ($, util, session) {
+ var playback = util.Module("playback");
+ var assert = util.assert;
+
+ var ALWAYS_REPLAY = {
+ "cursor-update": true,
+ "scroll-update": true
+ };
+
+ playback.getLogs = function (url) {
+ var result = $.Deferred();
+ $.ajax({
+ url: url,
+ dataType: "text"
+ }).then(
+ function (logs) {
+ logs = parseLogs(logs);
+ result.resolve(logs);
+ },
+ function (error) {
+ result.reject(error);
+ });
+ return result;
+ };
+
+ function parseLogs(logs) {
+ logs = logs.replace(/\r\n/g, '\n');
+ logs = logs.split(/\n/g);
+ var result = [];
+ for (var i=0; i<logs.length; i++) {
+ var line = logs[i];
+ line = line.replace(/^\s+/, "").replace(/\s+$/, "");
+ if (line.search(/\/\*/) === 0) {
+ var last = line.search(/\*\//);
+ if (last == -1) {
+ console.warn("bad line:", line);
+ continue;
+ }
+ line = line.substr(last+2);
+ }
+ line = line.replace(/^\s+/, "");
+ if (! line) {
+ continue;
+ }
+ line = JSON.parse(line);
+ result.push(line);
+ }
+ return Logs(result);
+ }
+
+ var Logs = util.Class({
+ constructor: function (logs, fromStorage) {
+ this.logs = logs;
+ this.fromStorage = fromStorage;
+ this.pos = 0;
+ },
+
+ play: function () {
+ this.start = Date.now();
+ if (this.pos >= this.logs.length) {
+ this.unload();
+ return;
+ }
+ if (this.pos !== 0) {
+ // First we need to play the hello
+ var toReplay = [];
+ var foundHello = false;
+ for (var i=this.pos-1; i>=0; i--) {
+ var item = this.logs[i];
+ if (ALWAYS_REPLAY[item.type]) {
+ toReplay.push(item);
+ }
+ if (item.type == "hello" || item.type == "hello-back") {
+ this.playItem(item);
+ foundHello = true;
+ break;
+ }
+ }
+ if (! foundHello) {
+ console.warn("No hello message found before position", this.pos);
+ }
+ toReplay.reverse();
+ for (i=0; i<toReplay.length; i++) {
+ this.playItem(toReplay[i]);
+ }
+ }
+ this.playOne();
+ },
+
+ cancel: function () {
+ if (this.playTimer) {
+ clearTimeout(this.playTimer);
+ this.playTimer = null;
+ }
+ this.start = null;
+ this.pos = 0;
+ this.unload();
+ },
+
+ pause: function () {
+ if (this.playTimer) {
+ clearTimeout(this.playTimer);
+ this.playTimer = null;
+ }
+ },
+
+ playOne: function () {
+ this.playTimer = null;
+ if (this.pos >= this.logs.length) {
+ this.unload();
+ return;
+ }
+ var item = this.logs[this.pos];
+ this.playItem(item);
+ this.pos++;
+ if (this.pos >= this.logs.length) {
+ this.unload();
+ return;
+ }
+ var next = this.logs[this.pos];
+ var pause = next.date - item.date;
+ this.playTimer = setTimeout(this.playOne.bind(this), pause);
+ if (this.fromStorage) {
+ this.savePos();
+ }
+ },
+
+ playItem: function (item) {
+ if (item.type == "hello") {
+ // We may need to pause here
+ if (item.url != (location.href+"").replace(/\#.*/, "")) {
+ this.pause();
+ }
+ }
+ try {
+ session._getChannel().onmessage(item);
+ } catch (e) {
+ console.warn("Could not play back message:", item, "error:", e);
+ }
+ },
+
+ save: function () {
+ this.fromStorage = true;
+ session.setStorage("playback.logs", this.logs);
+ this.savePos();
+ },
+
+ savePos: function () {
+ session.setStorage("playback.pos", this.pos);
+ },
+
+ unload: function () {
+ if (this.fromStorage) {
+ session.setStorage("playback.logs");
+ session.setStorage("playback.pos");
+ }
+ // FIXME: should do a bye message here
+ }
+
+ });
+
+ playback.getRunningLogs = function () {
+ var value = session.getStorage("playback.logs");
+ if (! value) {
+ return null;
+ }
+ var logs = Logs(value, true);
+ var pos = session.getStorage("playback.pos") || 0;
+ logs.pos = pos;
+ return logs;
+ };
+
+ return playback;
+});
Oops, something went wrong.

0 comments on commit 218b582

Please sign in to comment.