playback-ui.js 6.2 KB

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