mirror of
https://github.com/YusufB5/ASCILINE.git
synced 2026-06-23 22:48:06 +02:00
feat: server-side frame dropping under client backpressure (#30)
The live WebSocket pushed every frame on a wall-clock schedule regardless of whether the client could keep up. On a slow device frames piled into the client decode queue, and the client paid the inflate+delta-patch cost for each one before dropping the excess in its render loop. CPU spent on frames never shown. Client now reports its decoded-frame backlog (frameBuffer depth) ~4x/sec over the existing command channel. When the backlog exceeds BACKLOG_HIGH the server skips frames: it advances the source cheaply (grab, no decode/encode/send) so video stays time-aligned with audio, and crucially holds prev_frame across the gap so the next sent frame is a correct delta against the last SENT frame. No keyframe resync needed - deltas are always relative to the last sent frame. MAX_CONSEC_DROPS caps the gap and guarantees liveness for slow/non-reporting clients. Fully backward compatible: a client that never reports keeps backlog=0 and behaviour is unchanged. test/test_backpressure_gap.js encodes a keyframe + a dropped gap via codec.py and decodes through the shipped codec.js, asserting the post-gap frame is reconstructed bit-exact (and is a real DELTA), matching the no-drop path.
This commit is contained in:
parent
cacf262d61
commit
d9480e9f85
4 changed files with 238 additions and 0 deletions
21
app.js
21
app.js
|
|
@ -40,6 +40,7 @@ function formatTime(seconds) {
|
|||
// ── STATE ──
|
||||
let state = 'IDLE'; // IDLE | PLAYING | PAUSED
|
||||
let ws = null;
|
||||
let bufferReportTimer = null; // periodic backlog report to the server (backpressure)
|
||||
const frameBuffer = [];
|
||||
const BUFFER_SIZE = 4;
|
||||
let codecDecoder = null; // Adaptive codec decoder (codec.js)
|
||||
|
|
@ -237,6 +238,7 @@ function connectWebSocket() {
|
|||
lastRenderTime = performance.now();
|
||||
lastFpsUpdate = lastRenderTime;
|
||||
requestAnimationFrame(renderFrame);
|
||||
startBufferReports();
|
||||
};
|
||||
|
||||
if (audioEl) {
|
||||
|
|
@ -421,8 +423,27 @@ function renderFrame(now) {
|
|||
// CLEANUP
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
// ── BACKPRESSURE REPORTING ──
|
||||
// Tell the server how many decoded frames are queued for render (frameBuffer
|
||||
// depth). When it grows the client is behind, and the server drops frames
|
||||
// server-side instead of making us decode (inflate + delta-patch) frames we
|
||||
// would only drop after. ~4 Hz is plenty: the server only needs a coarse signal.
|
||||
function startBufferReports() {
|
||||
stopBufferReports();
|
||||
bufferReportTimer = setInterval(() => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN && state === 'PLAYING') {
|
||||
ws.send(JSON.stringify({ type: 'buffer', depth: frameBuffer.length }));
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function stopBufferReports() {
|
||||
if (bufferReportTimer) { clearInterval(bufferReportTimer); bufferReportTimer = null; }
|
||||
}
|
||||
|
||||
function finishStream() {
|
||||
state = 'IDLE';
|
||||
stopBufferReports();
|
||||
if (ws) { ws.onclose = null; ws.close(); ws = null; }
|
||||
if (audioEl) { audioEl.pause(); audioEl.src = ''; }
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue