fix: improve montage review playback smoothness and fix video seek overshoot

Fix FFmpeg initial seek overshooting to a future keyframe when the
requested timestamp falls between keyframes. After the initial
AVSEEK_FLAG_FRAME seek, detect if the returned frame is past the
target and re-seek backward to get the correct keyframe before it.

On the JS side, preserve fractional seconds throughout the playback
pipeline: remove Math.floor() truncation in mmove() and setSpeed(),
and use parseFloat instead of parseInt for currentTimeSecs
initialization. Reduce initial display interval from 1000ms to 100ms
for ~10fps refresh rate during review playback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Isaac Connor
2026-02-26 13:16:07 -05:00
parent 61b025b85f
commit 2b14e9044e
4 changed files with 18 additions and 7 deletions

View File

@@ -283,6 +283,18 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
Warning("Unable to get frame.");
return nullptr;
}
// If the initial seek landed past the target (on a future keyframe),
// re-seek backward to get the keyframe before the target instead.
if (frame->pts > seek_target) {
Debug(1, "Initial seek overshot: frame pts %" PRId64 " > seek_target %" PRId64 ", seeking backward",
frame->pts, seek_target);
ret = av_seek_frame(input_format_context, stream_id, seek_target,
AVSEEK_FLAG_BACKWARD);
if (ret >= 0) {
get_frame(stream_id);
}
}
} // end if ! frame
if (

View File

@@ -825,8 +825,8 @@ function mmove(event) {
if ( mouseisdown ) {
// only do anything if the mouse is depressed while on the sheet
const relx = event.target.relMouseCoords(event).x;
const sec = Math.floor(minTimeSecs + rangeTimeSecs / event.target.width * relx);
if (parseInt(sec)) outputUpdate(sec);
const sec = minTimeSecs + rangeTimeSecs / event.target.width * relx;
if (sec) outputUpdate(sec);
}
}
@@ -890,7 +890,7 @@ function setSpeed(speed_index) {
}
currentSpeed = parseFloat(speeds[speed_index]);
speedIndex = speed_index;
playSecsPerInterval = Math.floor( 1000 * currentSpeed * currentDisplayInterval ) / 1000000;
playSecsPerInterval = currentSpeed * currentDisplayInterval / 1000;
setCookie('speed', speedIndex);
showSpeed(speed_index);
timerFire();
@@ -1475,7 +1475,6 @@ function loadFrames(zm_events) {
if (last_frame && (frame.EventId != last_frame.EventId)) {
last_frame = null;
}
//console.log(date, frame.TimeStamp, frame.Delta, frame.TimeStampSecs);
if (last_frame) {
frame.PrevFrameId = last_frame.Id;
last_frame.NextFrameId = frame.Id;

View File

@@ -177,9 +177,9 @@ var maxTime='$maxTime';
";
echo 'var rangeTimeSecs='.($maxTimeSecs - $minTimeSecs + 1).";\n";
if ( isset($defaultCurrentTimeSecs) ) {
echo 'var currentTimeSecs=parseInt('.$defaultCurrentTimeSecs.");\n";
echo 'var currentTimeSecs=parseFloat('.$defaultCurrentTimeSecs.");\n";
} else {
echo 'var currentTimeSecs=parseInt('.$minTimeSecs.");\n";
echo 'var currentTimeSecs=parseFloat('.$minTimeSecs.");\n";
}
echo 'var speeds=[';

View File

@@ -243,7 +243,7 @@ for ( $i = 0; $i < count($speeds); $i++ ) {
}
}
$initialDisplayInterval = 1000;
$initialDisplayInterval = 100;
if (isset($_REQUEST['displayinterval']))
$initialDisplayInterval = validCardinal($_REQUEST['displayinterval']);