Add configurable FPS to test preview and fix composite stream release race
- Add FPS control (1-60, default 20) to test preview modal next to LED count - Server accepts fps query param, controls frame send interval - Single Apply icon button (✓) applies both LED count and FPS - FPS control stays visible for picture sources (LED count hidden) - Fix composite sub-stream consumer ID collision: use unique instance ID to prevent old WebSocket release from killing new connection's streams Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -125,7 +125,7 @@ import {
|
||||
onNotificationFilterModeChange,
|
||||
notificationAddAppColor, notificationRemoveAppColor,
|
||||
testNotification,
|
||||
testColorStrip, closeTestCssSourceModal, applyCssTestLedCount, fireCssTestNotification, fireCssTestNotificationLayer,
|
||||
testColorStrip, closeTestCssSourceModal, applyCssTestSettings, fireCssTestNotification, fireCssTestNotificationLayer,
|
||||
} from './features/color-strips.js';
|
||||
|
||||
// Layer 5: audio sources
|
||||
@@ -405,7 +405,7 @@ Object.assign(window, {
|
||||
onNotificationFilterModeChange,
|
||||
notificationAddAppColor, notificationRemoveAppColor,
|
||||
testNotification,
|
||||
testColorStrip, closeTestCssSourceModal, applyCssTestLedCount, fireCssTestNotification, fireCssTestNotificationLayer,
|
||||
testColorStrip, closeTestCssSourceModal, applyCssTestSettings, fireCssTestNotification, fireCssTestNotificationLayer,
|
||||
|
||||
// audio sources
|
||||
showAudioSourceModal,
|
||||
|
||||
@@ -1886,6 +1886,7 @@ export async function stopCSSOverlay(cssId) {
|
||||
/* ── Test / Preview ───────────────────────────────────────────── */
|
||||
|
||||
const _CSS_TEST_LED_KEY = 'css_test_led_count';
|
||||
const _CSS_TEST_FPS_KEY = 'css_test_fps';
|
||||
let _cssTestWs = null;
|
||||
let _cssTestRaf = null;
|
||||
let _cssTestLatestRgb = null;
|
||||
@@ -1901,6 +1902,11 @@ function _getCssTestLedCount() {
|
||||
return (stored > 0 && stored <= 2000) ? stored : 100;
|
||||
}
|
||||
|
||||
function _getCssTestFps() {
|
||||
const stored = parseInt(localStorage.getItem(_CSS_TEST_FPS_KEY), 10);
|
||||
return (stored >= 1 && stored <= 60) ? stored : 20;
|
||||
}
|
||||
|
||||
export function testColorStrip(sourceId) {
|
||||
// Clean up any previous session fully
|
||||
if (_cssTestWs) { _cssTestWs.close(); _cssTestWs = null; }
|
||||
@@ -1920,31 +1926,37 @@ export function testColorStrip(sourceId) {
|
||||
document.getElementById('css-test-strip-view').style.display = 'none';
|
||||
document.getElementById('css-test-rect-view').style.display = 'none';
|
||||
document.getElementById('css-test-layers-view').style.display = 'none';
|
||||
document.getElementById('css-test-led-control').style.display = '';
|
||||
document.getElementById('css-test-led-group').style.display = '';
|
||||
const layersContainer = document.getElementById('css-test-layers');
|
||||
if (layersContainer) layersContainer.innerHTML = '';
|
||||
document.getElementById('css-test-status').style.display = '';
|
||||
document.getElementById('css-test-status').textContent = t('color_strip.test.connecting');
|
||||
|
||||
// Restore LED count + Enter key handler
|
||||
// Restore LED count + FPS + Enter key handlers
|
||||
const ledCount = _getCssTestLedCount();
|
||||
const ledInput = document.getElementById('css-test-led-input');
|
||||
ledInput.value = ledCount;
|
||||
ledInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); applyCssTestLedCount(); } };
|
||||
ledInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); applyCssTestSettings(); } };
|
||||
|
||||
_cssTestConnect(sourceId, ledCount);
|
||||
const fpsVal = _getCssTestFps();
|
||||
const fpsInput = document.getElementById('css-test-fps-input');
|
||||
fpsInput.value = fpsVal;
|
||||
fpsInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); applyCssTestSettings(); } };
|
||||
|
||||
_cssTestConnect(sourceId, ledCount, fpsVal);
|
||||
}
|
||||
|
||||
function _cssTestConnect(sourceId, ledCount) {
|
||||
function _cssTestConnect(sourceId, ledCount, fps) {
|
||||
// Close existing connection if any
|
||||
if (_cssTestWs) { _cssTestWs.close(); _cssTestWs = null; }
|
||||
|
||||
// Bump generation so any late messages from old WS are ignored
|
||||
const gen = ++_cssTestGeneration;
|
||||
|
||||
if (!fps) fps = _getCssTestFps();
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const apiKey = localStorage.getItem('wled_api_key') || '';
|
||||
const wsUrl = `${protocol}//${window.location.host}/api/v1/color-strip-sources/${sourceId}/test/ws?token=${encodeURIComponent(apiKey)}&led_count=${ledCount}`;
|
||||
const wsUrl = `${protocol}//${window.location.host}/api/v1/color-strip-sources/${sourceId}/test/ws?token=${encodeURIComponent(apiKey)}&led_count=${ledCount}&fps=${fps}`;
|
||||
|
||||
_cssTestWs = new WebSocket(wsUrl);
|
||||
_cssTestWs.binaryType = 'arraybuffer';
|
||||
@@ -1978,7 +1990,7 @@ function _cssTestConnect(sourceId, ledCount) {
|
||||
if (modalContent) modalContent.style.maxWidth = isPicture ? '900px' : '';
|
||||
|
||||
// Hide LED count control for picture sources (LED count is fixed by calibration)
|
||||
document.getElementById('css-test-led-control').style.display = isPicture ? 'none' : '';
|
||||
document.getElementById('css-test-led-group').style.display = isPicture ? 'none' : '';
|
||||
|
||||
// Show fire button for notification sources (direct only; composite has per-layer buttons)
|
||||
const isNotify = _cssTestMeta.source_type === 'notification';
|
||||
@@ -2115,14 +2127,22 @@ function _cssTestUpdateBrightness(values) {
|
||||
}
|
||||
}
|
||||
|
||||
export function applyCssTestLedCount() {
|
||||
const input = document.getElementById('css-test-led-input');
|
||||
if (!input || !_cssTestSourceId) return;
|
||||
let val = parseInt(input.value, 10);
|
||||
if (isNaN(val) || val < 1) val = 1;
|
||||
if (val > 2000) val = 2000;
|
||||
input.value = val;
|
||||
localStorage.setItem(_CSS_TEST_LED_KEY, String(val));
|
||||
export function applyCssTestSettings() {
|
||||
if (!_cssTestSourceId) return;
|
||||
|
||||
const ledInput = document.getElementById('css-test-led-input');
|
||||
let leds = parseInt(ledInput?.value, 10);
|
||||
if (isNaN(leds) || leds < 1) leds = 1;
|
||||
if (leds > 2000) leds = 2000;
|
||||
if (ledInput) ledInput.value = leds;
|
||||
localStorage.setItem(_CSS_TEST_LED_KEY, String(leds));
|
||||
|
||||
const fpsInput = document.getElementById('css-test-fps-input');
|
||||
let fps = parseInt(fpsInput?.value, 10);
|
||||
if (isNaN(fps) || fps < 1) fps = 1;
|
||||
if (fps > 60) fps = 60;
|
||||
if (fpsInput) fpsInput.value = fps;
|
||||
localStorage.setItem(_CSS_TEST_FPS_KEY, String(fps));
|
||||
|
||||
// Clear frame data but keep views/layout intact to avoid size jump
|
||||
_cssTestLatestRgb = null;
|
||||
@@ -2130,7 +2150,7 @@ export function applyCssTestLedCount() {
|
||||
_cssTestLayerData = null;
|
||||
|
||||
// Reconnect (generation counter ignores stale frames from old WS)
|
||||
_cssTestConnect(_cssTestSourceId, val);
|
||||
_cssTestConnect(_cssTestSourceId, leds, fps);
|
||||
}
|
||||
|
||||
function _cssTestRenderLoop() {
|
||||
|
||||
Reference in New Issue
Block a user