playback.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2018 The noVNC Authors
  4. * Licensed under MPL 2.0 (see LICENSE.txt)
  5. */
  6. import RFB from '../core/rfb.js';
  7. import * as Log from '../core/util/logging.js';
  8. // Immediate polyfill
  9. if (window.setImmediate === undefined) {
  10. let _immediateIdCounter = 1;
  11. const _immediateFuncs = {};
  12. window.setImmediate = (func) => {
  13. const index = _immediateIdCounter++;
  14. _immediateFuncs[index] = func;
  15. window.postMessage("noVNC immediate trigger:" + index, "*");
  16. return index;
  17. };
  18. window.clearImmediate = (id) => {
  19. _immediateFuncs[id];
  20. };
  21. window.addEventListener("message", (event) => {
  22. if ((typeof event.data !== "string") ||
  23. (event.data.indexOf("noVNC immediate trigger:") !== 0)) {
  24. return;
  25. }
  26. const index = event.data.slice("noVNC immediate trigger:".length);
  27. const callback = _immediateFuncs[index];
  28. if (callback === undefined) {
  29. return;
  30. }
  31. delete _immediateFuncs[index];
  32. callback();
  33. });
  34. }
  35. class FakeWebSocket {
  36. constructor() {
  37. this.binaryType = "arraybuffer";
  38. this.protocol = "";
  39. this.readyState = "open";
  40. this.onerror = () => {};
  41. this.onmessage = () => {};
  42. this.onopen = () => {};
  43. }
  44. send() {
  45. }
  46. close() {
  47. }
  48. }
  49. export default class RecordingPlayer {
  50. constructor(frames, disconnected) {
  51. this._frames = frames;
  52. this._disconnected = disconnected;
  53. this._rfb = undefined;
  54. this._frameLength = this._frames.length;
  55. this._frameIndex = 0;
  56. this._startTime = undefined;
  57. this._realtime = true;
  58. this._trafficManagement = true;
  59. this._running = false;
  60. this.onfinish = () => {};
  61. }
  62. run(realtime, trafficManagement) {
  63. // initialize a new RFB
  64. this._ws = new FakeWebSocket();
  65. this._rfb = new RFB(document.getElementById('VNC_screen'), this._ws);
  66. this._rfb.viewOnly = true;
  67. this._rfb.addEventListener("disconnect",
  68. this._handleDisconnect.bind(this));
  69. this._rfb.addEventListener("credentialsrequired",
  70. this._handleCredentials.bind(this));
  71. // reset the frame index and timer
  72. this._frameIndex = 0;
  73. this._startTime = (new Date()).getTime();
  74. this._realtime = realtime;
  75. this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
  76. this._running = true;
  77. this._queueNextPacket();
  78. }
  79. _queueNextPacket() {
  80. if (!this._running) { return; }
  81. let frame = this._frames[this._frameIndex];
  82. // skip send frames
  83. while (this._frameIndex < this._frameLength && frame.fromClient) {
  84. this._frameIndex++;
  85. frame = this._frames[this._frameIndex];
  86. }
  87. if (this._frameIndex >= this._frameLength) {
  88. Log.Debug('Finished, no more frames');
  89. this._finish();
  90. return;
  91. }
  92. if (this._realtime) {
  93. const toffset = (new Date()).getTime() - this._startTime;
  94. let delay = frame.timestamp - toffset;
  95. if (delay < 1) delay = 1;
  96. setTimeout(this._doPacket.bind(this), delay);
  97. } else {
  98. setImmediate(this._doPacket.bind(this));
  99. }
  100. }
  101. _doPacket() {
  102. // Avoid having excessive queue buildup in non-realtime mode
  103. if (this._trafficManagement && this._rfb._flushing) {
  104. const orig = this._rfb._display.onflush;
  105. this._rfb._display.onflush = () => {
  106. this._rfb._display.onflush = orig;
  107. this._rfb._onFlush();
  108. this._doPacket();
  109. };
  110. return;
  111. }
  112. const frame = this._frames[this._frameIndex];
  113. this._ws.onmessage({'data': frame.data});
  114. this._frameIndex++;
  115. this._queueNextPacket();
  116. }
  117. _finish() {
  118. if (this._rfb._display.pending()) {
  119. this._rfb._display.onflush = () => {
  120. if (this._rfb._flushing) {
  121. this._rfb._onFlush();
  122. }
  123. this._finish();
  124. };
  125. this._rfb._display.flush();
  126. } else {
  127. this._running = false;
  128. this._ws.onclose({code: 1000, reason: ""});
  129. delete this._rfb;
  130. this.onfinish((new Date()).getTime() - this._startTime);
  131. }
  132. }
  133. _handleDisconnect(evt) {
  134. this._running = false;
  135. this._disconnected(evt.detail.clean, this._frameIndex);
  136. }
  137. _handleCredentials(evt) {
  138. this._rfb.sendCredentials({"username": "Foo",
  139. "password": "Bar",
  140. "target": "Baz"});
  141. }
  142. }