203 lines
5.9 KiB
JavaScript
203 lines
5.9 KiB
JavaScript
/*
|
|
* noVNC: HTML5 VNC client
|
|
* Copyright (C) 2012 Joel Martin
|
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
|
*/
|
|
|
|
import RFB from '../core/rfb.js';
|
|
import * as Log from '../core/util/logging.js';
|
|
import Base64 from '../core/base64.js';
|
|
|
|
// Immediate polyfill
|
|
if (setImmediate === undefined) {
|
|
var _immediateIdCounter = 1;
|
|
var _immediateFuncs = {};
|
|
|
|
var setImmediate = function (func) {
|
|
var index = _immediateIdCounter++;
|
|
_immediateFuncs[index] = func;
|
|
window.postMessage("noVNC immediate trigger:" + index, "*");
|
|
return index;
|
|
};
|
|
|
|
window.clearImmediate = function (id) {
|
|
_immediateFuncs[id];
|
|
};
|
|
|
|
var _onMessage = function (event) {
|
|
if ((typeof event.data !== "string") ||
|
|
(event.data.indexOf("noVNC immediate trigger:") !== 0)) {
|
|
return;
|
|
}
|
|
|
|
var index = event.data.slice("noVNC immediate trigger:".length);
|
|
|
|
var callback = _immediateFuncs[index];
|
|
if (callback === undefined) {
|
|
return;
|
|
}
|
|
|
|
delete _immediateFuncs[index];
|
|
|
|
callback();
|
|
};
|
|
window.addEventListener("message", _onMessage);
|
|
}
|
|
|
|
export default function RecordingPlayer (frames, encoding, disconnected, notification) {
|
|
this._frames = frames;
|
|
this._encoding = encoding;
|
|
|
|
this._disconnected = disconnected;
|
|
this._notification = notification;
|
|
|
|
if (this._encoding === undefined) {
|
|
let frame = this._frames[0];
|
|
let start = frame.indexOf('{', 1) + 1;
|
|
if (frame.slice(start).startsWith('UkZC')) {
|
|
this._encoding = 'base64';
|
|
} else {
|
|
this._encoding = 'binary';
|
|
}
|
|
}
|
|
|
|
this._rfb = undefined;
|
|
this._frame_length = this._frames.length;
|
|
|
|
this._frame_index = 0;
|
|
this._start_time = undefined;
|
|
this._realtime = true;
|
|
this._trafficManagement = true;
|
|
|
|
this._running = false;
|
|
|
|
this.onfinish = function () {};
|
|
}
|
|
|
|
RecordingPlayer.prototype = {
|
|
run: function (realtime, trafficManagement) {
|
|
// initialize a new RFB
|
|
this._rfb = new RFB(document.getElementById('VNC_canvas'),
|
|
{'view_only': true,
|
|
'onDisconnected': this._handleDisconnect.bind(this),
|
|
'onNotification': this._notification});
|
|
this._enablePlaybackMode();
|
|
|
|
// reset the frame index and timer
|
|
this._frame_index = 0;
|
|
this._start_time = (new Date()).getTime();
|
|
|
|
this._realtime = realtime;
|
|
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
|
|
|
|
this._running = true;
|
|
|
|
// launch the tests
|
|
this._rfb.connect('wss://test');
|
|
|
|
this._queueNextPacket();
|
|
},
|
|
|
|
// _enablePlaybackMode mocks out things not required for running playback
|
|
_enablePlaybackMode: function () {
|
|
this._rfb._sock.send = function (arr) {};
|
|
this._rfb._sock.close = function () {};
|
|
this._rfb._sock.flush = function () {};
|
|
this._rfb._checkEvents = function () {};
|
|
this._rfb.connect = function (url) {
|
|
this._url = url;
|
|
this._rfb_credentials = {};
|
|
this._sock.init('binary', 'ws');
|
|
this._rfb_connection_state = 'connecting';
|
|
this._rfb_init_state = 'ProtocolVersion';
|
|
};
|
|
},
|
|
|
|
_queueNextPacket: function () {
|
|
if (!this._running) { return; }
|
|
|
|
var frame = this._frames[this._frame_index];
|
|
|
|
// skip send frames
|
|
while (this._frame_index < this._frame_length && frame.charAt(0) === "}") {
|
|
this._frame_index++;
|
|
frame = this._frames[this._frame_index];
|
|
}
|
|
|
|
if (frame === 'EOF') {
|
|
Log.Debug('Finished, found EOF');
|
|
this._finish();
|
|
return;
|
|
}
|
|
|
|
if (this._frame_index >= this._frame_length) {
|
|
Log.Debug('Finished, no more frames');
|
|
this._finish();
|
|
return;
|
|
}
|
|
|
|
if (this._realtime) {
|
|
let foffset = frame.slice(1, frame.indexOf('{', 1));
|
|
let toffset = (new Date()).getTime() - this._start_time;
|
|
let delay = foffset - toffset;
|
|
if (delay < 1) delay = 1;
|
|
|
|
setTimeout(this._doPacket.bind(this), delay);
|
|
} else {
|
|
setImmediate(this._doPacket.bind(this));
|
|
}
|
|
},
|
|
|
|
_doPacket: function () {
|
|
// Avoid having excessive queue buildup in non-realtime mode
|
|
if (this._trafficManagement && this._rfb._flushing) {
|
|
let player = this;
|
|
let orig = this._rfb._display.get_onFlush();
|
|
this._rfb._display.set_onFlush(function () {
|
|
player._rfb._display.set_onFlush(orig);
|
|
player._rfb._onFlush();
|
|
player._doPacket();
|
|
});
|
|
return;
|
|
}
|
|
|
|
const frame = this._frames[this._frame_index];
|
|
var start = frame.indexOf('{', 1) + 1;
|
|
if (this._encoding === 'base64') {
|
|
var u8 = Base64.decode(frame.slice(start));
|
|
start = 0;
|
|
} else {
|
|
var u8 = new Uint8Array(frame.length - start);
|
|
for (let i = 0; i < frame.length - start; i++) {
|
|
u8[i] = frame.charCodeAt(start + i);
|
|
}
|
|
}
|
|
|
|
this._rfb._sock._recv_message({'data': u8});
|
|
this._frame_index++;
|
|
|
|
this._queueNextPacket();
|
|
},
|
|
|
|
_finish() {
|
|
if (this._rfb._display.pending()) {
|
|
var player = this;
|
|
this._rfb._display.set_onFlush(function () {
|
|
if (player._rfb._flushing) {
|
|
player._rfb._onFlush();
|
|
}
|
|
player._finish();
|
|
});
|
|
this._rfb._display.flush();
|
|
} else {
|
|
this._running = false;
|
|
this.onfinish((new Date()).getTime() - this._start_time);
|
|
}
|
|
},
|
|
|
|
_handleDisconnect(rfb, reason) {
|
|
this._running = false;
|
|
this._disconnected(rfb, reason, this._frame_index);
|
|
}
|
|
};
|