From 7bb0e9024a301f3a5b44107365a50592e0e03dda Mon Sep 17 00:00:00 2001 From: Pine Date: Mon, 17 Aug 2026 21:59:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=A7=E5=B1=8F?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=8E=A7=E5=88=B6=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端: - useMqttControl 指令分发补上 play_target(此前被静默丢弃,立即播放无法触发大屏跳转) - MediaScreen 重构 playTarget:pendingTargetRef 统一刷新播放列表路径, 消除与 playlist_changed 重载的竞态(避免跳转被旧索引覆盖) 后端: - storage.add_to_playlist 自动识别 URL 来源(source=url), 修复 URL 媒体加入播放列表后被当本地文件过滤、永远无法播放的问题 - _load 时规范化旧数据(source=None 的 http 条目自动补 url) --- backend/app/storage.py | 12 ++++++++++-- src/pages/MediaScreen.jsx | 30 +++++++++++++++--------------- src/utils/useMqttControl.js | 1 + 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/backend/app/storage.py b/backend/app/storage.py index f6271cf..cf3dc6f 100644 --- a/backend/app/storage.py +++ b/backend/app/storage.py @@ -30,7 +30,13 @@ class Storage: def _load(self): if self.data_file.exists(): try: - return json.loads(self.data_file.read_text("utf-8")) + data = json.loads(self.data_file.read_text("utf-8")) + # 规范化:URL 来源自动标记(兼容旧数据 source=None 的 URL 条目) + for it in data.setdefault("playlist", []): + p = it.get("path", "") + if p.startswith(("http://", "https://")) and not it.get("source"): + it["source"] = "url" + return data except Exception: pass return {"settings": dict(DEFAULT_SETTINGS), "playlist": [], "url_media": []} @@ -67,7 +73,9 @@ class Storage: with self._lock: items = self._data.setdefault("playlist", []) if not any(it.get("path") == path for it in items): - items.append({"path": path, "source": None, "name": None}) + # URL 来源自动标记,避免被当作本地文件过滤 + source = "url" if path.startswith(("http://", "https://")) else None + items.append({"path": path, "source": source, "name": None}) self._save() def remove_from_playlist(self, path): diff --git a/src/pages/MediaScreen.jsx b/src/pages/MediaScreen.jsx index 24d2741..a561a2e 100644 --- a/src/pages/MediaScreen.jsx +++ b/src/pages/MediaScreen.jsx @@ -228,6 +228,8 @@ export default function MediaScreen() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [skip]); + const pendingTargetRef = useRef(null); + const reloadPlaylist = useCallback(async () => { try { const data = await api.getPlaylist(); @@ -246,7 +248,12 @@ export default function MediaScreen() { setLoaded(true); let newIndex = -1; - if (currentItem) { + const target = pendingTargetRef.current; + if (target) { + // 指定播放优先:跳转到目标媒体(一次性) + pendingTargetRef.current = null; + newIndex = newList.findIndex(item => item.relative_path === target); + } else if (currentItem) { newIndex = newList.findIndex(item => item.relative_path === currentItem.relative_path); } if (newIndex >= 0) { @@ -259,6 +266,13 @@ export default function MediaScreen() { } catch {/* ignore */} }, []); + // 指定媒体立即播放:先标记目标,再刷新播放列表(统一路径,避免竞态) + const playTarget = useCallback(async (path) => { + pendingTargetRef.current = path; + await reloadPlaylist(); + setPaused(false); + }, [reloadPlaylist]); + const applySettings = useCallback((msg) => { if (msg.volume !== undefined) { const vol = msg.volume / 100; @@ -295,20 +309,6 @@ export default function MediaScreen() { } }, []); - // 指定媒体立即播放:刷新播放列表并跳转到目标项 - const playTarget = useCallback(async (path) => { - try { - const data = await api.getPlaylist(); - const newList = data.files || []; - if (newList.length === 0) return; - setList(newList); - setLoaded(true); - const idx = newList.findIndex((it) => it.relative_path === path); - if (idx >= 0) setIndex(idx); - setPaused(false); - } catch { /* ignore */ } - }, []); - // MQTT 控制通道(后端 opc/display/command → window 事件) useEffect(() => { const handler = (e) => { diff --git a/src/utils/useMqttControl.js b/src/utils/useMqttControl.js index af007b6..de0dc43 100644 --- a/src/utils/useMqttControl.js +++ b/src/utils/useMqttControl.js @@ -81,6 +81,7 @@ export function useMqttControl() { case 'next': case 'prev': case 'set_mode': + case 'play_target': case 'minimize': case 'settings_changed': case 'playlist_changed':