Add svdemo_seekexact for precise seeking with read-forward

svdemo_seek snaps to the nearest keyframe (fast, ±interval accuracy).
svdemo_seekexact reads forward from the keyframe to the exact target
time, giving frame-accurate positioning at the cost of a brief
processing delay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
serge_shubin 2026-03-24 19:38:22 +08:00
parent a8bfa738b0
commit d0a4310bad
3 changed files with 72 additions and 0 deletions

View file

@ -356,6 +356,7 @@ void SVD_CleanupPlayback( void );
void SVD_Stop_f( void );
void SVD_Pause_f( void );
void SVD_Seek_f( void );
void SVD_SeekExact_f( void );
qboolean SVD_PlaybackFrame( void );
qboolean SVD_IsRecording( void );
qboolean SVD_IsPlaying( void );

View file

@ -746,6 +746,7 @@ void SV_AddOperatorCommands( void ) {
Cmd_AddCommand ("svdemo_play", SVD_Play_f);
Cmd_AddCommand ("svdemo_pause", SVD_Pause_f);
Cmd_AddCommand ("svdemo_seek", SVD_Seek_f);
Cmd_AddCommand ("svdemo_seekexact", SVD_SeekExact_f);
}
/*

View file

@ -1104,6 +1104,76 @@ void SVD_Seek_f( void ) {
Com_Printf( "Seeked to time %d.\n", svs.time );
}
void SVD_SeekExact_f( void ) {
int targetTime, i, bestKf;
float seconds;
if ( !demo.playing ) {
Com_Printf( "Not playing a server demo.\n" );
return;
}
if ( Cmd_Argc() < 2 ) {
Com_Printf( "Usage: svdemo_seekexact <seconds>\n" );
return;
}
if ( demo.numKeyframes <= 0 ) {
Com_Printf( "No keyframes in this demo.\n" );
return;
}
seconds = atof( Cmd_Argv(1) );
targetTime = svs.time + (int)(seconds * 1000);
// find nearest keyframe at or before target time
bestKf = -1;
for ( i = 0; i < demo.numKeyframes; i++ ) {
if ( demo.keyframeTimes[i] <= targetTime ) {
bestKf = i;
} else {
break;
}
}
if ( bestKf < 0 ) {
bestKf = 0;
targetTime = demo.keyframeTimes[0];
}
// seek to keyframe
FS_Seek( demo.playFile, demo.keyframeOffsets[bestKf], FS_SEEK_SET );
Com_Memset( demo.playPrevEntities, 0, sizeof(demo.playPrevEntities) );
Com_Memset( demo.playPrevPlayers, 0, sizeof(demo.playPrevPlayers) );
svs.time = demo.keyframeTimes[bestKf];
svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
demo.seeked = qtrue;
demo.endOfDemo = qfalse;
// read forward from keyframe to target time
while ( svs.time < targetTime ) {
if ( !SVD_ReadFrame( demo.playFile ) ) {
demo.endOfDemo = qtrue;
break;
}
}
// reset client snapshot timing
{
int j;
for ( j = 0; j < sv_maxclients->integer; j++ ) {
if ( svs.clients[j].state >= CS_ACTIVE ) {
svs.clients[j].nextSnapshotTime = svs.time;
}
}
}
sv.timeResidual = 1000 / sv_fps->integer;
Com_Printf( "Seeked to time %d (read forward %d ms from keyframe).\n",
svs.time, svs.time - demo.keyframeTimes[bestKf] );
}
/*
Called from SV_Frame() to advance playback by one frame.
Returns qtrue if a frame was read, qfalse if demo ended.