]>
Commit | Line | Data |
---|---|---|
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 (setImmediate === undefined) { | |
13 | var _immediateIdCounter = 1; | |
14 | var _immediateFuncs = {}; | |
15 | ||
16 | var setImmediate = function (func) { | |
17 | var 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 | var _onMessage = function (event) { | |
28 | if ((typeof event.data !== "string") || | |
29 | (event.data.indexOf("noVNC immediate trigger:") !== 0)) { | |
30 | return; | |
31 | } | |
32 | ||
33 | var index = event.data.slice("noVNC immediate trigger:".length); | |
34 | ||
35 | var 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 | let frame = this._frames[0]; | |
55 | let 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 | var 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 | let foffset = frame.slice(1, frame.indexOf('{', 1)); | |
133 | let 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 | let player = this; | |
147 | let 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 | var start = frame.indexOf('{', 1) + 1; | |
158 | if (this._encoding === 'base64') { | |
159 | var u8 = Base64.decode(frame.slice(start)); | |
160 | start = 0; | |
161 | } else { | |
162 | var u8 = new Uint8Array(frame.length - start); | |
163 | for (let i = 0; i < frame.length - start; i++) { | |
164 | u8[i] = frame.charCodeAt(start + i); | |
165 | } | |
166 | } | |
167 | ||
168 | this._rfb._sock._recv_message({'data': u8}); | |
169 | this._frame_index++; | |
170 | ||
171 | this._queueNextPacket(); | |
172 | }, | |
173 | ||
174 | _finish() { | |
175 | if (this._rfb._display.pending()) { | |
176 | var player = this; | |
177 | this._rfb._display.onflush = function () { | |
178 | if (player._rfb._flushing) { | |
179 | player._rfb._onFlush(); | |
180 | } | |
181 | player._finish(); | |
182 | }; | |
183 | this._rfb._display.flush(); | |
184 | } else { | |
185 | this._running = false; | |
186 | this._rfb._sock._eventHandlers.close({code: 1000, reason: ""}); | |
187 | delete this._rfb; | |
188 | this.onfinish((new Date()).getTime() - this._start_time); | |
189 | } | |
190 | }, | |
191 | ||
192 | _handleDisconnect(evt) { | |
193 | this._running = false; | |
194 | this._disconnected(evt.detail.clean, this._frame_index); | |
195 | } | |
196 | }; |