]>
Commit | Line | Data |
---|---|---|
8727f598 JD |
1 | /* global VNC_frame_data, VNC_frame_encoding */ |
2 | ||
d6c17390 SR |
3 | import * as WebUtil from '../app/webutil.js'; |
4 | import RecordingPlayer from './playback.js'; | |
527a1fd0 | 5 | import Base64 from '../core/base64.js'; |
d6c17390 | 6 | |
2b5f94fa | 7 | let frames = null; |
d6c17390 SR |
8 | |
9 | function message(str) { | |
2b5f94fa | 10 | const cell = document.getElementById('messages'); |
d6c17390 SR |
11 | cell.textContent += str + "\n"; |
12 | cell.scrollTop = cell.scrollHeight; | |
13 | } | |
14 | ||
15 | function loadFile() { | |
16 | const fname = WebUtil.getQueryVar('data', null); | |
17 | ||
18 | if (!fname) { | |
19 | return Promise.reject("Must specify data=FOO in query string."); | |
20 | } | |
21 | ||
527a1fd0 | 22 | message("Loading " + fname + "..."); |
7c1f1a9c | 23 | |
651c23ec | 24 | return new Promise((resolve, reject) => { |
2b5f94fa | 25 | const script = document.createElement("script"); |
7c1f1a9c PO |
26 | script.onload = resolve; |
27 | script.onerror = reject; | |
28 | document.body.appendChild(script); | |
29 | script.src = "../recordings/" + fname; | |
30 | }); | |
d6c17390 SR |
31 | } |
32 | ||
7c1f1a9c | 33 | function enableUI() { |
2b5f94fa | 34 | const iterations = WebUtil.getQueryVar('iterations', 3); |
d6c17390 SR |
35 | document.getElementById('iterations').value = iterations; |
36 | ||
2b5f94fa | 37 | const mode = WebUtil.getQueryVar('mode', 3); |
d6c17390 SR |
38 | if (mode === 'realtime') { |
39 | document.getElementById('mode2').checked = true; | |
40 | } else { | |
41 | document.getElementById('mode1').checked = true; | |
42 | } | |
43 | ||
527a1fd0 | 44 | message("Loaded " + VNC_frame_data.length + " frames"); |
d6c17390 SR |
45 | |
46 | const startButton = document.getElementById('startButton'); | |
afb621d5 | 47 | startButton.disabled = false; |
d6c17390 SR |
48 | startButton.addEventListener('click', start); |
49 | ||
527a1fd0 PO |
50 | message("Converting..."); |
51 | ||
7c1f1a9c | 52 | frames = VNC_frame_data; |
527a1fd0 PO |
53 | |
54 | let encoding; | |
b05a7b1d | 55 | // Only present in older recordings |
426a8c92 | 56 | if (window.VNC_frame_encoding) { |
b05a7b1d | 57 | encoding = VNC_frame_encoding; |
527a1fd0 PO |
58 | } else { |
59 | let frame = frames[0]; | |
60 | let start = frame.indexOf('{', 1) + 1; | |
568f6567 | 61 | if (frame.slice(start, start+4) === 'UkZC') { |
527a1fd0 PO |
62 | encoding = 'base64'; |
63 | } else { | |
64 | encoding = 'binary'; | |
65 | } | |
66 | } | |
67 | ||
68 | for (let i = 0;i < frames.length;i++) { | |
69 | let frame = frames[i]; | |
70 | ||
71 | if (frame === "EOF") { | |
72 | frames.splice(i); | |
73 | break; | |
74 | } | |
75 | ||
76 | let dataIdx = frame.indexOf('{', 1) + 1; | |
77 | ||
78 | let time = parseInt(frame.slice(1, dataIdx - 1)); | |
79 | ||
80 | let u8; | |
81 | if (encoding === 'base64') { | |
82 | u8 = Base64.decode(frame.slice(dataIdx)); | |
83 | } else { | |
84 | u8 = new Uint8Array(frame.length - dataIdx); | |
85 | for (let j = 0; j < frame.length - dataIdx; j++) { | |
86 | u8[j] = frame.charCodeAt(dataIdx + j); | |
87 | } | |
88 | } | |
89 | ||
90 | frames[i] = { fromClient: frame[0] === '}', | |
91 | timestamp: time, | |
92 | data: u8 }; | |
426a8c92 | 93 | } |
527a1fd0 PO |
94 | |
95 | message("Ready"); | |
d6c17390 SR |
96 | } |
97 | ||
0e4808bf | 98 | class IterationPlayer { |
527a1fd0 | 99 | constructor(iterations, frames) { |
0e4808bf | 100 | this._iterations = iterations; |
d6c17390 | 101 | |
0e4808bf JD |
102 | this._iteration = undefined; |
103 | this._player = undefined; | |
d6c17390 | 104 | |
0e4808bf | 105 | this._start_time = undefined; |
d6c17390 | 106 | |
0e4808bf | 107 | this._frames = frames; |
d6c17390 | 108 | |
0e4808bf | 109 | this._state = 'running'; |
d6c17390 | 110 | |
651c23ec JD |
111 | this.onfinish = () => {}; |
112 | this.oniterationfinish = () => {}; | |
113 | this.rfbdisconnected = () => {}; | |
0e4808bf | 114 | } |
d6c17390 | 115 | |
568f6567 | 116 | start(realtime) { |
d6c17390 SR |
117 | this._iteration = 0; |
118 | this._start_time = (new Date()).getTime(); | |
119 | ||
568f6567 | 120 | this._realtime = realtime; |
d6c17390 SR |
121 | |
122 | this._nextIteration(); | |
0e4808bf | 123 | } |
d6c17390 | 124 | |
0e4808bf | 125 | _nextIteration() { |
527a1fd0 | 126 | const player = new RecordingPlayer(this._frames, this._disconnected.bind(this)); |
d6c17390 SR |
127 | player.onfinish = this._iterationFinish.bind(this); |
128 | ||
129 | if (this._state !== 'running') { return; } | |
130 | ||
131 | this._iteration++; | |
132 | if (this._iteration > this._iterations) { | |
133 | this._finish(); | |
134 | return; | |
135 | } | |
136 | ||
568f6567 | 137 | player.run(this._realtime, false); |
0e4808bf | 138 | } |
d6c17390 | 139 | |
0e4808bf | 140 | _finish() { |
d6c17390 SR |
141 | const endTime = (new Date()).getTime(); |
142 | const totalDuration = endTime - this._start_time; | |
143 | ||
e3557022 PO |
144 | const evt = new CustomEvent('finish', |
145 | { detail: | |
146 | { duration: totalDuration, | |
147 | iterations: this._iterations } } ); | |
d6c17390 | 148 | this.onfinish(evt); |
0e4808bf | 149 | } |
d6c17390 | 150 | |
0e4808bf | 151 | _iterationFinish(duration) { |
e3557022 PO |
152 | const evt = new CustomEvent('iterationfinish', |
153 | { detail: | |
154 | { duration: duration, | |
155 | number: this._iteration } } ); | |
d6c17390 SR |
156 | this.oniterationfinish(evt); |
157 | ||
158 | this._nextIteration(); | |
0e4808bf | 159 | } |
d6c17390 | 160 | |
0e4808bf | 161 | _disconnected(clean, frame) { |
d472f3f1 | 162 | if (!clean) { |
d6c17390 SR |
163 | this._state = 'failed'; |
164 | } | |
165 | ||
e3557022 PO |
166 | const evt = new CustomEvent('rfbdisconnected', |
167 | { detail: | |
168 | { clean: clean, | |
169 | frame: frame, | |
170 | iteration: this._iteration } } ); | |
d6c17390 | 171 | this.onrfbdisconnected(evt); |
0e4808bf JD |
172 | } |
173 | } | |
d6c17390 SR |
174 | |
175 | function start() { | |
176 | document.getElementById('startButton').value = "Running"; | |
177 | document.getElementById('startButton').disabled = true; | |
178 | ||
179 | const iterations = document.getElementById('iterations').value; | |
180 | ||
568f6567 | 181 | let realtime; |
d6c17390 SR |
182 | |
183 | if (document.getElementById('mode1').checked) { | |
184 | message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`); | |
568f6567 | 185 | realtime = false; |
d6c17390 SR |
186 | } else { |
187 | message(`Starting realtime playback [${iterations} iteration(s)]`); | |
568f6567 | 188 | realtime = true; |
d6c17390 SR |
189 | } |
190 | ||
527a1fd0 | 191 | const player = new IterationPlayer(iterations, frames); |
651c23ec | 192 | player.oniterationfinish = (evt) => { |
e3557022 | 193 | message(`Iteration ${evt.detail.number} took ${evt.detail.duration}ms`); |
d6c17390 | 194 | }; |
651c23ec | 195 | player.onrfbdisconnected = (evt) => { |
e3557022 PO |
196 | if (!evt.detail.clean) { |
197 | message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`); | |
d6c17390 SR |
198 | } |
199 | }; | |
651c23ec | 200 | player.onfinish = (evt) => { |
e3557022 PO |
201 | const iterTime = parseInt(evt.detail.duration / evt.detail.iterations, 10); |
202 | message(`${evt.detail.iterations} iterations took ${evt.detail.duration}ms (average ${iterTime}ms / iteration)`); | |
d6c17390 SR |
203 | |
204 | document.getElementById('startButton').disabled = false; | |
205 | document.getElementById('startButton').value = "Start"; | |
206 | }; | |
568f6567 | 207 | player.start(realtime); |
d6c17390 SR |
208 | } |
209 | ||
651c23ec | 210 | loadFile().then(enableUI).catch(e => message("Error loading recording: " + e)); |