GUACAMOLE-1803: Fix frame scheduling logic in recording playback.

This commit is contained in:
James Muehlner
2023-06-20 19:11:16 +00:00
parent ad9f90718c
commit 8497878cb2

View File

@@ -149,13 +149,13 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
var currentFrame = -1; var currentFrame = -1;
/** /**
* The timestamp of the frame when playback began, in milliseconds. If * The position of the recording when playback began, in milliseconds. If
* playback is not in progress, this will be null. * playback is not in progress, this will be null.
* *
* @private * @private
* @type {number} * @type {number}
*/ */
var startVideoTimestamp = null; var startVideoPosition = null;
/** /**
* The real-world timestamp when playback began, in milliseconds. If * The real-world timestamp when playback began, in milliseconds. If
@@ -750,21 +750,31 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
// Pull the upcoming frame // Pull the upcoming frame
var next = frames[currentFrame + 1]; var next = frames[currentFrame + 1];
// The position at which the next frame should be rendered, taking // The number of elapsed milliseconds on the clock since playback began
// into account any accumulated delays from rendering frames so far var realLifePlayTime = Date.now() - startRealTimestamp;
var nextFramePosition = next.timestamp - startVideoTimestamp + startRealTimestamp;
// The position at which the refresh interval would induce an // The number of milliseconds between the recording position when
// update to the current recording position, rounded to the nearest // playback started and the position of the next frame
// whole multiple of refreshInterval to ensure consistent timing var timestampOffset = (
// for refresh intervals even with inconsistent frame timing toRelativeTimestamp(next.timestamp) - startVideoPosition);
var nextRefreshPosition = refreshInterval > 0 ? refreshInterval * (
Math.floor((currentPosition + refreshInterval) / refreshInterval) // The delay until the next frame should be rendered, taking into
) : nextFramePosition; // account any accumulated delays from rendering frames so far
var nextFrameDelay = timestampOffset - realLifePlayTime;
// The delay until the refresh interval would induce an update to
// the current recording position, rounded to the nearest whole
// multiple of refreshInterval to ensure consistent timing for
// refresh intervals even with inconsistent frame timing
var nextRefreshDelay = refreshInterval >= 0
? (refreshInterval * (Math.floor(
(currentPosition + refreshInterval) / refreshInterval))
) - currentPosition
: nextFrameDelay;
// If the next frame will occur before the next refresh interval, // If the next frame will occur before the next refresh interval,
// advance to the frame after the appropriate delay // advance to the frame after the appropriate delay
if (nextFramePosition <= nextRefreshPosition) if (nextFrameDelay <= nextRefreshDelay)
seekToFrame(currentFrame + 1, function frameDelayElapsed() { seekToFrame(currentFrame + 1, function frameDelayElapsed() {
@@ -772,7 +782,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
lastUpdateTimestamp = Date.now(); lastUpdateTimestamp = Date.now();
continuePlayback(); continuePlayback();
}, Date.now() + (nextFramePosition - currentPosition)); }, Date.now() + nextFrameDelay);
// The position needs to be incremented before the next frame // The position needs to be incremented before the next frame
else { else {
@@ -785,7 +795,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
updateTimeout = null; updateTimeout = null;
// Update the position // Update the position
currentPosition = nextRefreshPosition; currentPosition += nextRefreshDelay;
// Notifiy the new position using the onseek handler // Notifiy the new position using the onseek handler
if (recording.onseek) if (recording.onseek)
@@ -795,7 +805,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
lastUpdateTimestamp = Date.now(); lastUpdateTimestamp = Date.now();
continuePlayback(); continuePlayback();
}, nextRefreshPosition - currentPosition); }, nextRefreshDelay);
} }
} }
@@ -944,7 +954,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
* true if playback is currently in progress, false otherwise. * true if playback is currently in progress, false otherwise.
*/ */
this.isPlaying = function isPlaying() { this.isPlaying = function isPlaying() {
return !!startVideoTimestamp; return !!startRealTimestamp;
}; };
/** /**
@@ -956,13 +966,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
*/ */
this.getPosition = function getPosition() { this.getPosition = function getPosition() {
// Position is simply zero if playback has not started at all return currentPosition;
if (currentFrame === -1)
return 0;
// Return current position as a millisecond timestamp relative to the
// start of the recording
return toRelativeTimestamp(frames[currentFrame].timestamp);
}; };
@@ -1006,8 +1010,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
// Store timestamp of playback start for relative scheduling of // Store timestamp of playback start for relative scheduling of
// future frames // future frames
var next = frames[currentFrame + 1]; startVideoPosition = currentPosition;
startVideoTimestamp = next.timestamp;
startRealTimestamp = Date.now(); startRealTimestamp = Date.now();
// Begin playback of video // Begin playback of video
@@ -1125,7 +1128,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
// Playback is stopped // Playback is stopped
lastUpdateTimestamp = null; lastUpdateTimestamp = null;
startVideoTimestamp = null; startVideoPosition = null;
startRealTimestamp = null; startRealTimestamp = null;
} }