Tune frame queue and viewport sizing

This commit is contained in:
2026-06-11 10:29:07 -07:00
parent de0307539c
commit 2866d33dec
3 changed files with 16 additions and 5 deletions

View File

@@ -70,8 +70,8 @@ Relay mode uses bounded per-worker input queues so one branch can briefly lag wi
The UI intentionally hides these settings, but the backend still supports them through `POST /api/session`.
- Frame rate defaults to `24fps`. Lower it if the client cannot keep up.
- Max width defaults to `960px`. Lower it first if bandwidth or image decode is the bottleneck.
- JPEG quality uses ffmpeg's `-q:v` scale, where lower is better. Set the default with `JPEG_QUALITY`; `7` is the fallback, `2` is high quality, and `18` is rough but lighter.
- Max width defaults to `960px`, and the client now caps each session to its viewport width. Lower `DEFAULT_FRAME_WIDTH` first if bandwidth or image decode is the bottleneck.
- JPEG quality uses ffmpeg's `-q:v` scale, where lower is better/larger. Set the default with `JPEG_QUALITY`; `7` is the fallback, `2` is high quality, and `18` is rough but much lighter.
- Audio defaults to stereo MP3 at `160k`. Tune with `DEFAULT_AUDIO_BITRATE`, `DEFAULT_AUDIO_CHANNELS`, and `DEFAULT_AUDIO_SAMPLE_RATE`.
## Tradeoffs

View File

@@ -95,7 +95,7 @@ elements.form.addEventListener('submit', async (event) => {
const response = await fetch('/api/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: elements.url.value }),
body: JSON.stringify({ url: elements.url.value, width: getViewportFrameWidth() }),
});
const payload = await response.json();
@@ -752,7 +752,7 @@ function trimPendingFrameQueue() {
if (overflow > 0) {
noteClientTelemetry('pendingQueueOverflowFrames', overflow);
noteClientTelemetryMax('pendingQueuePeakFrames', state.pendingFrames.length);
state.pendingFrames.splice(0, overflow);
state.pendingFrames.splice(maxQueuedFrames, overflow);
}
}
@@ -766,7 +766,7 @@ function trimFrameQueue() {
return;
}
const removed = state.frames.splice(0, overflow);
const removed = state.frames.splice(maxQueuedFrames, overflow);
noteClientTelemetry('decodedQueueOverflowFrames', overflow);
noteClientTelemetryMax('decodedQueuePeakFrames', state.frames.length + overflow);
@@ -816,6 +816,15 @@ function getFrameQueueLimit(seconds, minimum) {
return Math.max(minimum, Math.ceil(fps * seconds));
}
function getViewportFrameWidth() {
const width = Math.ceil(Math.max(
document.documentElement.clientWidth || 0,
window.innerWidth || 0,
));
return clampNumber(width, 160, 1920);
}
function isLateFrame(timestamp) {
if (!state.session || state.isSeeking || elements.audio.paused || elements.audio.readyState === 0) {
return false;

View File

@@ -133,6 +133,8 @@ app.post('/api/session', async (request, response) => {
lastUsedAt: Date.now(),
});
logInfo(`session created id=${shortId(id)} mode=${getSessionPlaybackConnectionMode(sessions.get(id))} fps=${options.fps} width=${options.width} quality=${options.quality}`);
if (METADATA_PROBE_ENABLED) {
startSessionMetadataProbe(sessions.get(id));
}