fix: improve playback timing and progress tracking in player.handlebars

Signed-off-by: si458 <simonsmith5521@gmail.com>

si458 committed Mar 23, 2026 at 14:53 UTC b99a21cf13f4ae153dd7726d87c3c20da331800e
1 file changed +55 -5
views/player.handlebars
+55 -5
@@ -115,6 +115,10 @@
115 var readState = 0;
116 var waitTimer = null;
117 var waitTimerArgs = null;
118 + var waitTickTimer = null;
119 + var waitTickStartWall = 0;
120 + var waitTickStartRec = 0;
121 + var waitTickTargetRec = 0;
122 var deskAspectRatio = 0;
123 var currentDeltaTimeTotalSec = 0;
124 var videoWriter = null;
@@ -504,14 +508,41 @@
508 QS('progressbar').width = '0px';
509 }
510
511 + function updateWaitTick() {
512 + var elapsed = Date.now() - waitTickStartWall;
513 + var speed = parseFloat(Q('PlaySpeed').value);
514 + var recNow = waitTickStartRec + (elapsed / speed);
515 + if (recNow > waitTickTargetRec) recNow = waitTickTargetRec;
516 + var deltaTimeTotalSec = Math.floor((recNow - recFileStartTime) / 1000);
517 + if (currentDeltaTimeTotalSec != deltaTimeTotalSec) {
518 + currentDeltaTimeTotalSec = deltaTimeTotalSec;
519 + var hrs = Math.floor(deltaTimeTotalSec / 3600);
520 + var mins = Math.floor((deltaTimeTotalSec % 3600) / 60);
521 + var secs = Math.floor(deltaTimeTotalSec % 60);
522 + QH('timespan', pad2(hrs) + ':' + pad2(mins) + ':' + pad2(secs));
523 + }
524 + if (recFileEndTime > recFileStartTime) {
525 + QS('progressbar').width = Math.floor(((recNow - recFileStartTime) / (recFileEndTime - recFileStartTime)) * 100) + '%';
526 + }
527 + }
528 +
529 function processBlock(type, flags, time, data) {
530 if (type < 0) { pause(); return; }
531 var waitTime = Math.round((time - recFileLastTime) * parseFloat(Q('PlaySpeed').value));
532 if ((waitTime < 5) || (videoWriter != null)) {
533 processBlockEx(type, flags, time, data);
534 } else {
513 - waitTimerArgs = [type, flags, time, data]
514 - waitTimer = setTimeout(function () { waitTimer = null; if (waitTimerArgs) { processBlockEx(waitTimerArgs[0], waitTimerArgs[1], waitTimerArgs[2], waitTimerArgs[3]); } }, waitTime);
535 + waitTimerArgs = [type, flags, time, data];
536 + waitTickStartWall = Date.now();
537 + waitTickStartRec = recFileLastTime;
538 + waitTickTargetRec = time;
539 + if (waitTickTimer) { clearInterval(waitTickTimer); }
540 + waitTickTimer = setInterval(updateWaitTick, 250);
541 + waitTimer = setTimeout(function () {
542 + if (waitTickTimer) { clearInterval(waitTickTimer); waitTickTimer = null; }
543 + waitTimer = null;
544 + if (waitTimerArgs) { processBlockEx(waitTimerArgs[0], waitTimerArgs[1], waitTimerArgs[2], waitTimerArgs[3]); }
545 + }, waitTime);
546 }
547 }
548
@@ -631,6 +662,7 @@
662 currentDeltaTimeTotalSec = 0;
663 recFileEndTime = 0;
664 agentTerminal = null;
665 + if (waitTickTimer != null) { clearInterval(waitTickTimer); waitTickTimer = null; }
666 if (waitTimer != null) { clearTimeout(waitTimer); waitTimer = null; }
667 QH('deskstatus', '');
668 QE('PlayButton', false);
@@ -800,7 +832,14 @@
832 //agentTerminal = CreateAmtRemoteTerminal('Term', {});
833 //agentTerminal.State = 3;
834 }
803 - readNextBlock(processBlock);
835 + if (waitTimerArgs != null) {
836 + // Resuming mid-gap: restart timer for remaining time from current position
837 + var resumeArgs = waitTimerArgs;
838 + waitTimerArgs = null;
839 + processBlock(resumeArgs[0], resumeArgs[1], resumeArgs[2], resumeArgs[3]);
840 + } else {
841 + readNextBlock(processBlock);
842 + }
843 }
844
845 function pause() {
@@ -811,11 +850,15 @@
850 QE('PlayButton', recFilePtr != recFile.size);
851 QE('PauseButton', false);
852 QE('RestartButton', recFilePtr != 0);
853 + if (waitTickTimer != null) { clearInterval(waitTickTimer); waitTickTimer = null; }
854 if (waitTimer != null) {
855 clearTimeout(waitTimer);
856 waitTimer = null;
817 - processBlockEx(waitTimerArgs[0], waitTimerArgs[1], waitTimerArgs[2], waitTimerArgs[3]);
818 - waitTimerArgs = null;
857 + // Save current virtual time so resume starts from here, not from the pre-gap time
858 + var elapsed = Date.now() - waitTickStartWall;
859 + var speed = parseFloat(Q('PlaySpeed').value);
860 + recFileLastTime = Math.min(waitTickStartRec + (elapsed / speed), waitTickTargetRec);
861 + // Keep waitTimerArgs so play() can resume the gap from this point
862 }
863 }
864
@@ -825,6 +868,7 @@
868 if (playing == true) return;
869 recFilePtr = 0;
870 readState = 0;
871 + waitTimerArgs = null;
872 currentDeltaTimeTotalSec = 0;
873 QV('metadatadiv', true);
874 QE('PlayButton', true);
@@ -836,8 +880,14 @@
880 QV('TermParent', false);
881 QV('XTermParent', false);
882 clearXTerm();
883 + recFileLastTime = recFileStartTime;
884 if (agentDesktop) {
885 agentDesktop.Canvas.clearRect(0, 0, agentDesktop.CanvasId.width, agentDesktop.CanvasId.height);
886 + agentDesktop = CreateAgentRemoteDesktop('Desk');
887 + agentDesktop.onScreenSizeChange = deskAdjust;
888 + agentDesktop.onPreDrawImage = preCanvasDraw;
889 + agentDesktop.State = 3;
890 + deskAdjust();
891 } else if (amtDesktop) {
892 amtDesktop.canvas.clearRect(0, 0, amtDesktop.CanvasId.width, amtDesktop.CanvasId.height);
893 amtDesktop = CreateAmtRemoteDesktop('Desk');