GUACAMOLE-250: Automatically store keyframes while recordings are being played.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/commit/9d5e1111 Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/9d5e1111 Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/9d5e1111 Branch: refs/heads/master Commit: 9d5e1111a674ba0df66e6839488b1c5a261230f0 Parents: 1fcb5f2 Author: Michael Jumper <[email protected]> Authored: Sat Apr 15 16:04:12 2017 -0700 Committer: Michael Jumper <[email protected]> Committed: Sat Apr 15 17:12:29 2017 -0700 ---------------------------------------------------------------------- .../src/main/webapp/modules/SessionRecording.js | 100 ++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/9d5e1111/guacamole-common-js/src/main/webapp/modules/SessionRecording.js ---------------------------------------------------------------------- diff --git a/guacamole-common-js/src/main/webapp/modules/SessionRecording.js b/guacamole-common-js/src/main/webapp/modules/SessionRecording.js index 5922f17..aa9e335 100644 --- a/guacamole-common-js/src/main/webapp/modules/SessionRecording.js +++ b/guacamole-common-js/src/main/webapp/modules/SessionRecording.js @@ -42,6 +42,25 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) { 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. * * @private @@ -59,6 +78,24 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) { 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 * Guacamole.SessionRecording for playback of the session recording. * @@ -123,7 +160,9 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) { tunnel.oninstruction = function handleInstruction(opcode, args) { // 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 // frame as a new frame @@ -136,6 +175,16 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) { var frame = new Guacamole.SessionRecording._Frame(timestamp, instructions); 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 instructions = []; @@ -189,6 +238,13 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) { 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; + }); + } + }; /** @@ -472,6 +528,17 @@ Guacamole.SessionRecording = function SessionRecording(tunnel) { 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 * terminates the frame. * @@ -493,6 +560,7 @@ Guacamole.SessionRecording._Frame = function _Frame(timestamp, instructions) { * be null. * * @type {Object} + * @default null */ this.clientState = null; @@ -513,6 +581,14 @@ Guacamole.SessionRecording._Frame = function _Frame(timestamp, instructions) { 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. * * @type {String} @@ -526,6 +602,28 @@ Guacamole.SessionRecording._Frame.Instruction = function Instruction(opcode, arg */ 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; + + }; + }; /**
