]> git.proxmox.com Git - mirror_novnc.git/blob - tests/playback.js
cbeb20fdc199433cdd2c9deb54ac9ad4581b4b34
[mirror_novnc.git] / tests / playback.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Licensed under MPL 2.0 (see LICENSE.txt)
5 */
6
7 import RFB from '../core/rfb.js';
8 import * as Log from '../core/util/logging.js';
9 import Base64 from '../core/base64.js';
10
11 // Immediate polyfill
12 if (window.setImmediate === undefined) {
13 let _immediateIdCounter = 1;
14 const _immediateFuncs = {};
15
16 window.setImmediate = function (func) {
17 const index = _immediateIdCounter++;
18 _immediateFuncs[index] = func;
19 window.postMessage("noVNC immediate trigger:" + index, "*");
20 return index;
21 };
22
23 window.clearImmediate = function (id) {
24 _immediateFuncs[id];
25 };
26
27 const _onMessage = function (event) {
28 if ((typeof event.data !== "string") ||
29 (event.data.indexOf("noVNC immediate trigger:") !== 0)) {
30 return;
31 }
32
33 const index = event.data.slice("noVNC immediate trigger:".length);
34
35 const callback = _immediateFuncs[index];
36 if (callback === undefined) {
37 return;
38 }
39
40 delete _immediateFuncs[index];
41
42 callback();
43 };
44 window.addEventListener("message", _onMessage);
45 }
46
47 export default function RecordingPlayer (frames, encoding, disconnected) {
48 this._frames = frames;
49 this._encoding = encoding;
50
51 this._disconnected = disconnected;
52
53 if (this._encoding === undefined) {
54 const frame = this._frames[0];
55 const start = frame.indexOf('{', 1) + 1;
56 if (frame.slice(start).startsWith('UkZC')) {
57 this._encoding = 'base64';
58 } else {
59 this._encoding = 'binary';
60 }
61 }
62
63 this._rfb = undefined;
64 this._frame_length = this._frames.length;
65
66 this._frame_index = 0;
67 this._start_time = undefined;
68 this._realtime = true;
69 this._trafficManagement = true;
70
71 this._running = false;
72
73 this.onfinish = function () {};
74 }
75
76 RecordingPlayer.prototype = {
77 run: function (realtime, trafficManagement) {
78 // initialize a new RFB
79 this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
80 this._rfb.viewOnly = true;
81 this._rfb.addEventListener("disconnect",
82 this._handleDisconnect.bind(this));
83 this._enablePlaybackMode();
84
85 // reset the frame index and timer
86 this._frame_index = 0;
87 this._start_time = (new Date()).getTime();
88
89 this._realtime = realtime;
90 this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
91
92 this._running = true;
93
94 this._queueNextPacket();
95 },
96
97 // _enablePlaybackMode mocks out things not required for running playback
98 _enablePlaybackMode: function () {
99 this._rfb._sock.send = function (arr) {};
100 this._rfb._sock.close = function () {};
101 this._rfb._sock.flush = function () {};
102 this._rfb._sock.open = function () {
103 this.init();
104 this._eventHandlers.open();
105 };
106 },
107
108 _queueNextPacket: function () {
109 if (!this._running) { return; }
110
111 let frame = this._frames[this._frame_index];
112
113 // skip send frames
114 while (this._frame_index < this._frame_length && frame.charAt(0) === "}") {
115 this._frame_index++;
116 frame = this._frames[this._frame_index];
117 }
118
119 if (frame === 'EOF') {
120 Log.Debug('Finished, found EOF');
121 this._finish();
122 return;
123 }
124
125 if (this._frame_index >= this._frame_length) {
126 Log.Debug('Finished, no more frames');
127 this._finish();
128 return;
129 }
130
131 if (this._realtime) {
132 const foffset = frame.slice(1, frame.indexOf('{', 1));
133 const toffset = (new Date()).getTime() - this._start_time;
134 let delay = foffset - toffset;
135 if (delay < 1) delay = 1;
136
137 setTimeout(this._doPacket.bind(this), delay);
138 } else {
139 setImmediate(this._doPacket.bind(this));
140 }
141 },
142
143 _doPacket: function () {
144 // Avoid having excessive queue buildup in non-realtime mode
145 if (this._trafficManagement && this._rfb._flushing) {
146 const player = this;
147 const orig = this._rfb._display.onflush;
148 this._rfb._display.onflush = function () {
149 player._rfb._display.onflush = orig;
150 player._rfb._onFlush();
151 player._doPacket();
152 };
153 return;
154 }
155
156 const frame = this._frames[this._frame_index];
157 let start = frame.indexOf('{', 1) + 1;
158 let u8;
159 if (this._encoding === 'base64') {
160 u8 = Base64.decode(frame.slice(start));
161 start = 0;
162 } else {
163 u8 = new Uint8Array(frame.length - start);
164 for (let i = 0; i < frame.length - start; i++) {
165 u8[i] = frame.charCodeAt(start + i);
166 }
167 }
168
169 this._rfb._sock._recv_message({'data': u8});
170 this._frame_index++;
171
172 this._queueNextPacket();
173 },
174
175 _finish() {
176 if (this._rfb._display.pending()) {
177 const player = this;
178 this._rfb._display.onflush = function () {
179 if (player._rfb._flushing) {
180 player._rfb._onFlush();
181 }
182 player._finish();
183 };
184 this._rfb._display.flush();
185 } else {
186 this._running = false;
187 this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
188 delete this._rfb;
189 this.onfinish((new Date()).getTime() - this._start_time);
190 }
191 },
192
193 _handleDisconnect(evt) {
194 this._running = false;
195 this._disconnected(evt.detail.clean, this._frame_index);
196 }
197 };