witty/assets/replay.js
Zhi Wang 4b73e418e2 WIP
2022-01-18 13:06:25 -05:00

176 lines
4.6 KiB
JavaScript

// create a xterm for replay
function createReplayTerminal() {
// vscode-snazzy https://github.com/Tyriar/vscode-snazzy
// copied from xterm.js website
var baseTheme = {
foreground: '#eff0eb',
background: '#282a36',
selection: '#97979b33',
black: '#282a36',
brightBlack: '#686868',
red: '#ff5c57',
brightRed: '#ff5c57',
green: '#5af78e',
brightGreen: '#5af78e',
yellow: '#f3f99d',
brightYellow: '#f3f99d',
blue: '#57c7ff',
brightBlue: '#57c7ff',
magenta: '#ff6ac1',
brightMagenta: '#ff6ac1',
cyan: '#9aedfe',
brightCyan: '#9aedfe',
white: '#f1f1f0',
brightWhite: '#eff0eb'
};
const term = new Terminal({
fontFamily: `'Fira Code', ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace`,
fontSize: 12,
theme: baseTheme,
convertEol: true,
cursorBlink: true,
});
term.open(document.getElementById('terminal_view'));
term.resize(124, 37);
const weblinksAddon = new WebLinksAddon.WebLinksAddon();
term.loadAddon(weblinksAddon);
// fit the xterm viewpoint to parent element
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
fitAddon.fit();
return term;
}
// sleep for ms seconds
function _sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// we could sleep for a long time
// periodically check if we need to end replay.
// This is pretty ugly but the callback mess otherwise
async function sleep(ms, paused) {
var loop_cnt = parseInt(ms / 20) + 1
for (i = 0; i < loop_cnt; i++) {
if (paused()) {
return paused()
}
await _sleep(20)
}
return paused()
}
// convert data to uint8array, we cannot convert it to string as
// it will mess up special characters
function base64ToUint8array(base64) {
var raw = window.atob(base64);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for (i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}
async function play_ctrl(term, session, start, total_dur, paused, shifted, prog) {
var cur = 0
var new_pos = -1
start = parseInt(total_dur * start / 100)
term.reset()
for (const item of session) {
new_pos = shifted()
if (new_pos != -1) {
return new_pos
}
// we will blast through the beginning of the session
if (cur >= start) {
// we are cheating a little bit here, we do not want to wait for too long
exit = await sleep(Math.min(item.Duration, 800), paused)
if (exit) {
return
}
}
term.write(base64ToUint8array(item.Data))
cur += item.Duration
if (cur > start) {
prog(parseInt(cur * 100 / total_dur))
}
}
return -1
}
// replay session
// term: xterm, path: session file to replay,
// start: start position to replay in percentile, range 0-100
// paused: callback whether to stop play
// prog: callback to update the progress bar
// shift: callback whether we should change position
// end: callback when playback is finished
async function replay_session(term, path, start, paused, shifted, prog, end) {
var session
var total_dur = 0
var cur = 0
var new_pos = 0
var ret = 0
// read file from server
await fetch(path)
.then(res => res.json())
.then(out => {
session = out
})
//calculate the total duration
for (const item of session) {
item.Duration = parseInt(item.Duration / 1000000)
total_dur += item.Duration
}
console.log("Total duration:", total_dur, "start replay on position", start)
while (true) {
ret = await play_ctrl(term, session, start, total_dur, paused, shifted, prog)
if (ret == -1) {
break
}
start = ret
}
term.reset()
end()
}
function Init() {
let term = createReplayTerminal();
var str = [
' ┌────────────────────────────────────────────────────────────────────────────┐\n',
' │ \u001b[32;1mhttps://github.com/syssecfsu/witty\x1b[0m <- click it! │\n',
' └────────────────────────────────────────────────────────────────────────────┘\n',
''
].join('');
term.writeln(str);
// adjust the progress bar size to that of terminal
var view = document.querySelector("#terminal")
var pbar = document.querySelector("#replay-control")
pbar.setAttribute("style", "width:" + (view.offsetWidth - 32) + "px");
return term
}