mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-346: Avoid blocking the main thread when seeking within a session recording.
This commit is contained in:
@@ -60,6 +60,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.
|
||||
*
|
||||
@@ -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()) {
|
||||
|
||||
|
Reference in New Issue
Block a user