fix: 修复大屏播放控制链路
前端: - useMqttControl 指令分发补上 play_target(此前被静默丢弃,立即播放无法触发大屏跳转) - MediaScreen 重构 playTarget:pendingTargetRef 统一刷新播放列表路径, 消除与 playlist_changed 重载的竞态(避免跳转被旧索引覆盖) 后端: - storage.add_to_playlist 自动识别 URL 来源(source=url), 修复 URL 媒体加入播放列表后被当本地文件过滤、永远无法播放的问题 - _load 时规范化旧数据(source=None 的 http 条目自动补 url)
This commit is contained in:
+10
-2
@@ -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):
|
||||
|
||||
+15
-15
@@ -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) => {
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user