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