diff --git a/README.md b/README.md index 6e74d57..7503caa 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/public/app.js b/public/app.js index 83cf552..94c2c65 100644 --- a/public/app.js +++ b/public/app.js @@ -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; diff --git a/server/index.js b/server/index.js index 2b4577a..a87d39c 100644 --- a/server/index.js +++ b/server/index.js @@ -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)); }