Repository: incubator-guacamole-client
Updated Branches:
  refs/heads/master 682a6e49a -> 128b0ca8e


GUACAMOLE-346: Avoid blocking the main thread when seeking within a session 
recording.


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/1d6e8d22
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/1d6e8d22
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/1d6e8d22

Branch: refs/heads/master
Commit: 1d6e8d2216adbebc1c6377bf774a23a6762c877f
Parents: 33e76c4
Author: Michael Jumper <[email protected]>
Authored: Thu Jun 8 11:20:10 2017 -0700
Committer: Michael Jumper <[email protected]>
Committed: Sat Jul 15 16:07:33 2017 -0700

----------------------------------------------------------------------
 .../src/main/webapp/modules/SessionRecording.js | 110 ++++++++++++++++---
 1 file changed, 96 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/1d6e8d22/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 139597b..74eeb0b 100644
--- a/guacamole-common-js/src/main/webapp/modules/SessionRecording.js
+++ b/guacamole-common-js/src/main/webapp/modules/SessionRecording.js
@@ -61,6 +61,17 @@ Guacamole.SessionRecording = function 
SessionRecording(tunnel) {
     var KEYFRAME_TIME_INTERVAL = 5000;
 
     /**
+     * The maximum amount of time to spend in any particular seek operation
+     * before returning control to the main thread, in milliseconds. Seek
+     * operations exceeding this amount of time will proceed asynchronously.
+     *
+     * @private
+     * @constant
+     * @type {Number}
+     */
+    var MAXIMUM_SEEK_TIME = 5;
+
+    /**
      * All frames parsed from the provided tunnel.
      *
      * @private
@@ -150,6 +161,16 @@ Guacamole.SessionRecording = function 
SessionRecording(tunnel) {
      */
     var playbackTimeout = null;
 
+    /**
+     * The ID of the timeout which will continue the in-progress seek
+     * operation. If no seek operation is in progress, the ID stored here (if
+     * any) will not be valid.
+     *
+     * @private
+     * @type {Number}
+     */
+    var seekTimeout = null;
+
     // Start playback client connected
     playbackClient.connect();
 
@@ -294,17 +315,27 @@ Guacamole.SessionRecording = function 
SessionRecording(tunnel) {
 
     /**
      * Moves the playback position to the given frame, resetting the state of
-     * the playback client and replaying frames as necessary.
+     * the playback client and replaying frames as necessary. If the seek
+     * cannot be completed quickly, the seek operation may proceed
+     * asynchronously. If a seek operation is already in progress, that seek is
+     * first aborted. The progress of the seek operation can be observed
+     * through the onseek handler and the provided callback.
      *
      * @private
      * @param {Number} index
      *     The index of the frame which should become the new playback
      *     position.
+     *
+     * @param {function} [callback]
+     *     The callback to invoke once the seek operation has completed.
      */
-    var seekToFrame = function seekToFrame(index) {
+    var seekToFrame = function seekToFrame(index, callback) {
 
         var startIndex;
 
+        // Abort any in-progress seek
+        abortSeek();
+
         // Back up until startIndex represents current state
         for (startIndex = index; startIndex >= 0; startIndex--) {
 
@@ -327,17 +358,50 @@ Guacamole.SessionRecording = function 
SessionRecording(tunnel) {
         // Advance to frame index after current state
         startIndex++;
 
+        var startTime = new Date().getTime();
+
         // Replay any applicable incremental frames
-        for (; startIndex <= index; startIndex++)
+        for (; startIndex <= index; startIndex++) {
+
+            // Stop seeking if the operation is taking too long
+            var currentTime = new Date().getTime();
+            if (currentTime - startTime >= MAXIMUM_SEEK_TIME)
+                break;
+
             replayFrame(startIndex);
+        }
 
         // Current frame is now at requested index
-        currentFrame = index;
+        currentFrame = startIndex - 1;
 
         // Notify of changes in position
         if (recording.onseek)
             recording.onseek(recording.getPosition());
 
+        // If the seek operation has not yet completed, schedule continuation
+        if (currentFrame !== index)
+            seekTimeout = window.setTimeout(function continueSeek() {
+                seekToFrame(index, callback);
+            }, 0);
+
+        else {
+
+            // Notify that the requested seek has completed
+            if (callback)
+                callback();
+
+        }
+
+    };
+
+    /**
+     * Aborts the seek operation currently in progress, if any. If no seek
+     * operation is in progress, this function has no effect.
+     *
+     * @private
+     */
+    var abortSeek = function abortSeek() {
+        window.clearTimeout(seekTimeout);
     };
 
     /**
@@ -502,7 +566,9 @@ Guacamole.SessionRecording = function 
SessionRecording(tunnel) {
      * until no further frames exist. Playback is initially paused when a
      * Guacamole.SessionRecording is created, and must be explicitly started
      * through a call to this function. If playback is already in progress,
-     * this function has no effect.
+     * this function has no effect. If a seek operation is in progress,
+     * playback resumes at the current position, and the seek is aborted as if
+     * completed.
      */
     this.play = function play() {
 
@@ -531,12 +597,18 @@ Guacamole.SessionRecording = function 
SessionRecording(tunnel) {
      * Seeks to the given position within the recording. If the recording is
      * currently being played back, playback will continue after the seek is
      * performed. If the recording is currently paused, playback will be
-     * paused after the seek is performed.
+     * paused after the seek is performed. If a seek operation is already in
+     * progress, that seek is first aborted. Depending on how much processing
+     * the seek operation requires, the seek operation may proceed
+     * asynchronously.
      *
      * @param {Number} position
      *     The position within the recording to seek to, in milliseconds.
+     *
+     * @param {function} [callback]
+     *     The callback to invoke once the seek operation has completed.
      */
-    this.seek = function seek(position) {
+    this.seek = function seek(position, callback) {
 
         // Do not seek if no frames exist
         if (frames.length === 0)
@@ -547,22 +619,32 @@ Guacamole.SessionRecording = function 
SessionRecording(tunnel) {
         recording.pause();
 
         // Perform seek
-        seekToFrame(findFrame(0, frames.length - 1, position));
+        seekToFrame(findFrame(0, frames.length - 1, position), function 
restorePlaybackState() {
+
+            // Restore playback state
+            if (originallyPlaying)
+                recording.play();
 
-        // Restore playback state
-        if (originallyPlaying)
-            recording.play();
+            // Notify that seek has completed
+            if (callback)
+                callback();
+
+        });
 
     };
 
     /**
      * Pauses playback of the recording, if playback is currently in progress.
-     * If playback is not in progress, this function has no effect. Playback is
-     * initially paused when a Guacamole.SessionRecording is created, and must
-     * be explicitly started through a call to play().
+     * If playback is not in progress, this function has no effect. If a seek
+     * operation is in progress, the seek is aborted. Playback is initially
+     * paused when a Guacamole.SessionRecording is created, and must be
+     * explicitly started through a call to play().
      */
     this.pause = function pause() {
 
+        // Abort any in-progress seek
+        abortSeek();
+
         // Stop playback only if playback is in progress
         if (recording.isPlaying()) {
 

Reply via email to