mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 21:27:40 +00:00
GUACAMOLE-250: Automatically store keyframes while recordings are being played.
This commit is contained in:
@@ -41,6 +41,25 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) {
|
|||||||
*/
|
*/
|
||||||
var recording = this;
|
var recording = this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum number of characters which must have been read between
|
||||||
|
* keyframes.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @constant
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
var KEYFRAME_CHAR_INTERVAL = 16384;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum number of milliseconds which must elapse between keyframes.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @constant
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
var KEYFRAME_TIME_INTERVAL = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All frames parsed from the provided tunnel.
|
* All frames parsed from the provided tunnel.
|
||||||
*
|
*
|
||||||
@@ -58,6 +77,24 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) {
|
|||||||
*/
|
*/
|
||||||
var instructions = [];
|
var instructions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The approximate number of characters which have been read from the
|
||||||
|
* provided tunnel since the last frame was flagged for use as a keyframe.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
var charactersSinceLastKeyframe = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of the last frame which was flagged for use as a keyframe.
|
||||||
|
* If no timestamp has yet been flagged, this will be 0.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
var lastKeyframeTimestamp = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tunnel which feeds arbitrary instructions to the client used by this
|
* Tunnel which feeds arbitrary instructions to the client used by this
|
||||||
* Guacamole.SessionRecording for playback of the session recording.
|
* Guacamole.SessionRecording for playback of the session recording.
|
||||||
@@ -123,7 +160,9 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) {
|
|||||||
tunnel.oninstruction = function handleInstruction(opcode, args) {
|
tunnel.oninstruction = function handleInstruction(opcode, args) {
|
||||||
|
|
||||||
// Store opcode and arguments for received instruction
|
// Store opcode and arguments for received instruction
|
||||||
instructions.push(new Guacamole.SessionRecording._Frame.Instruction(opcode, args.slice()));
|
var instruction = new Guacamole.SessionRecording._Frame.Instruction(opcode, args.slice());
|
||||||
|
instructions.push(instruction);
|
||||||
|
charactersSinceLastKeyframe += instruction.getSize();
|
||||||
|
|
||||||
// Once a sync is received, store all instructions since the last
|
// Once a sync is received, store all instructions since the last
|
||||||
// frame as a new frame
|
// frame as a new frame
|
||||||
@@ -136,6 +175,16 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) {
|
|||||||
var frame = new Guacamole.SessionRecording._Frame(timestamp, instructions);
|
var frame = new Guacamole.SessionRecording._Frame(timestamp, instructions);
|
||||||
frames.push(frame);
|
frames.push(frame);
|
||||||
|
|
||||||
|
// This frame should eventually become a keyframe if enough data
|
||||||
|
// has been processed and enough recording time has elapsed, or if
|
||||||
|
// this is the absolute first frame
|
||||||
|
if (frames.length === 1 || (charactersSinceLastKeyframe >= KEYFRAME_CHAR_INTERVAL
|
||||||
|
&& timestamp - lastKeyframeTimestamp >= KEYFRAME_TIME_INTERVAL)) {
|
||||||
|
frame.keyframe = true;
|
||||||
|
lastKeyframeTimestamp = timestamp;
|
||||||
|
charactersSinceLastKeyframe = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear set of instructions in preparation for next frame
|
// Clear set of instructions in preparation for next frame
|
||||||
instructions = [];
|
instructions = [];
|
||||||
|
|
||||||
@@ -189,6 +238,13 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) {
|
|||||||
playbackTunnel.receiveInstruction(instruction.opcode, instruction.args);
|
playbackTunnel.receiveInstruction(instruction.opcode, instruction.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store client state if frame is flagged as a keyframe
|
||||||
|
if (frame.keyframe && !frame.clientState) {
|
||||||
|
playbackClient.exportState(function storeClientState(state) {
|
||||||
|
frame.clientState = state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -471,6 +527,17 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) {
|
|||||||
*/
|
*/
|
||||||
Guacamole.SessionRecording._Frame = function _Frame(timestamp, instructions) {
|
Guacamole.SessionRecording._Frame = function _Frame(timestamp, instructions) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this frame should be used as a keyframe if possible. This value
|
||||||
|
* is purely advisory. The stored clientState must eventually be manually
|
||||||
|
* set for the frame to be used as a keyframe. By default, frames are not
|
||||||
|
* keyframes.
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
this.keyframe = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timestamp of this frame, as dictated by the "sync" instruction which
|
* The timestamp of this frame, as dictated by the "sync" instruction which
|
||||||
* terminates the frame.
|
* terminates the frame.
|
||||||
@@ -493,6 +560,7 @@ Guacamole.SessionRecording._Frame = function _Frame(timestamp, instructions) {
|
|||||||
* be null.
|
* be null.
|
||||||
*
|
*
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
|
* @default null
|
||||||
*/
|
*/
|
||||||
this.clientState = null;
|
this.clientState = null;
|
||||||
|
|
||||||
@@ -512,6 +580,14 @@ Guacamole.SessionRecording._Frame = function _Frame(timestamp, instructions) {
|
|||||||
*/
|
*/
|
||||||
Guacamole.SessionRecording._Frame.Instruction = function Instruction(opcode, args) {
|
Guacamole.SessionRecording._Frame.Instruction = function Instruction(opcode, args) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to this Guacamole.SessionRecording._Frame.Instruction.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Guacamole.SessionRecording._Frame.Instruction}
|
||||||
|
*/
|
||||||
|
var instruction = this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The opcode of this Guacamole instruction.
|
* The opcode of this Guacamole instruction.
|
||||||
*
|
*
|
||||||
@@ -526,6 +602,28 @@ Guacamole.SessionRecording._Frame.Instruction = function Instruction(opcode, arg
|
|||||||
*/
|
*/
|
||||||
this.args = args;
|
this.args = args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the approximate number of characters which make up this
|
||||||
|
* instruction. This value is only approximate as it excludes the length
|
||||||
|
* prefixes and various delimiters used by the Guacamole protocol; only
|
||||||
|
* the content of the opcode and each argument is taken into account.
|
||||||
|
*
|
||||||
|
* @returns {Number}
|
||||||
|
* The approximate size of this instruction, in characters.
|
||||||
|
*/
|
||||||
|
this.getSize = function getSize() {
|
||||||
|
|
||||||
|
// Init with length of opcode
|
||||||
|
var size = instruction.opcode.length;
|
||||||
|
|
||||||
|
// Add length of all arguments
|
||||||
|
for (var i = 0; i < instruction.args.length; i++)
|
||||||
|
size += instruction.args[i].length;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user