diff --git a/src/app/components/DeviceDetailOverlay.tsx b/src/app/components/DeviceDetailOverlay.tsx index a4d37a7..c9d4f61 100644 --- a/src/app/components/DeviceDetailOverlay.tsx +++ b/src/app/components/DeviceDetailOverlay.tsx @@ -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(null); - const prevDeviceNumberRef = useRef(device.number); + const detectTokenRef = useRef(0); + // Cancel any in-flight detection and reset when navigating to a different device. useEffect(() => { - const deviceChanged = prevDeviceNumberRef.current !== device.number; - prevDeviceNumberRef.current = device.number; + ++detectTokenRef.current; + 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) { setMediaSetFiles(deviceData.media_set as MediaSetEntry[]); - return; } - // Don't run pattern detection when switching devices — only when the URL - // is actively being changed within the same device. - if (deviceChanged || !deviceData.url) { setMediaSetFiles(null); return; } - const match = (deviceData.url as string).match(/^(.+?)(\d+)(\.[^.]+)$/); - if (!match) { setMediaSetFiles(null); return; } + }, [deviceData.media_set]); + + // Pattern-detect sibling numbered files for the given URL. Called only when + // the user actively sets a URL — never on overlay load or device switch. + const detectMediaSet = async (url: string) => { + const token = ++detectTokenRef.current; + setMediaSetFiles(null); + const match = url.match(/^(.+?)(\d+)(\.[^.]+)$/); + if (!match) return; const [, prefix, , ext] = match; const candidates: string[] = []; for (let i = 1; i <= 10; i++) candidates.push(`${prefix}${i}${ext}`); - let cancelled = false; - Promise.all(candidates.map(f => stat(f).then(r => r !== null).catch(() => false))).then(flags => { - const found = candidates.filter((_, i) => flags[i]); - if (!cancelled) setMediaSetFiles(found.length > 1 ? found : null); - }); - return () => { cancelled = true; }; - }, [device.number, deviceData.url, deviceData.media_set]); + const flags = await Promise.all( + candidates.map(f => stat(f).then(r => r !== null).catch(() => false)) + ); + if (detectTokenRef.current !== token) return; + const found = candidates.filter((_, i) => flags[i]); + setMediaSetFiles(found.length > 1 ? found : null); + }; const switchMedia = (file: string) => { const path = getDevicePath();