feat(DeviceDetailOverlay): improve media set detection logic and optimize state management

This commit is contained in:
Jaime Idolpx 2026-06-12 05:02:18 -04:00
parent de303e9327
commit 5c28a69055

View File

@ -125,33 +125,40 @@ export default function DeviceDetailOverlay({
} }
}; };
// Prefer an explicit .lst-derived mediaSet stored in config; fall back to pattern detection.
const [mediaSetFiles, setMediaSetFiles] = useState<MediaSetEntry[] | null>(null); const [mediaSetFiles, setMediaSetFiles] = useState<MediaSetEntry[] | null>(null);
const prevDeviceNumberRef = useRef(device.number); const detectTokenRef = useRef(0);
// Cancel any in-flight detection and reset when navigating to a different device.
useEffect(() => { useEffect(() => {
const deviceChanged = prevDeviceNumberRef.current !== device.number; ++detectTokenRef.current;
prevDeviceNumberRef.current = device.number; setMediaSetFiles(null);
}, [device.number]);
// Sync config-backed media_set to display state whenever it is set explicitly
// (e.g. after a playlist is mounted).
useEffect(() => {
if (Array.isArray(deviceData.media_set) && deviceData.media_set.length > 1) { if (Array.isArray(deviceData.media_set) && deviceData.media_set.length > 1) {
setMediaSetFiles(deviceData.media_set as MediaSetEntry[]); setMediaSetFiles(deviceData.media_set as MediaSetEntry[]);
return;
} }
// Don't run pattern detection when switching devices — only when the URL }, [deviceData.media_set]);
// is actively being changed within the same device.
if (deviceChanged || !deviceData.url) { setMediaSetFiles(null); return; } // Pattern-detect sibling numbered files for the given URL. Called only when
const match = (deviceData.url as string).match(/^(.+?)(\d+)(\.[^.]+)$/); // the user actively sets a URL — never on overlay load or device switch.
if (!match) { setMediaSetFiles(null); return; } const detectMediaSet = async (url: string) => {
const token = ++detectTokenRef.current;
setMediaSetFiles(null);
const match = url.match(/^(.+?)(\d+)(\.[^.]+)$/);
if (!match) return;
const [, prefix, , ext] = match; const [, prefix, , ext] = match;
const candidates: string[] = []; const candidates: string[] = [];
for (let i = 1; i <= 10; i++) candidates.push(`${prefix}${i}${ext}`); for (let i = 1; i <= 10; i++) candidates.push(`${prefix}${i}${ext}`);
let cancelled = false; const flags = await Promise.all(
Promise.all(candidates.map(f => stat(f).then(r => r !== null).catch(() => false))).then(flags => { candidates.map(f => stat(f).then(r => r !== null).catch(() => false))
);
if (detectTokenRef.current !== token) return;
const found = candidates.filter((_, i) => flags[i]); const found = candidates.filter((_, i) => flags[i]);
if (!cancelled) setMediaSetFiles(found.length > 1 ? found : null); setMediaSetFiles(found.length > 1 ? found : null);
}); };
return () => { cancelled = true; };
}, [device.number, deviceData.url, deviceData.media_set]);
const switchMedia = (file: string) => { const switchMedia = (file: string) => {
const path = getDevicePath(); const path = getDevicePath();