fix(ncm): 歌单详情LoadFailed——track/all端点不存在(404)回退detail+分批song_detail已验证链路; 列表误触改按下锁定行+拖动抑制 (agent-227, 真机验证256首出曲) + perf(meowpanel): 屏幕帧整数定点DCT(无FPU软浮点3.96s→0.19s/帧,实测4-5fps)+前端链式轮询防堆积+实测fps显示 (agent-228)

ncm→73422166 已部署; meowpanel→f310a00e 已部署
This commit is contained in:
MeowOS committed 2026-08-31 17:40:04 +08:00
1 parent d394c73a7c
commit c041facd89
13 files changed
+447 -65

No files matched your search

+27 -10
View File
@@ -805,42 +805,59 @@
});
}
/* ---------------- 10. 实时屏幕 / 点击遥控 ---------------- */
/* ---------------- 10. 实时屏幕 / 点击遥控 ----------------
* 轮询改「上一帧到了再取下一帧」(链式 setTimeout,绝不堆积/重叠):
* 每帧下载完成后按目标周期自校正 delay(周期 = max(0, interval - 请求耗时)),
* 故名义 fps 被尊重,且请求变慢时自动退化为 1/请求耗时。 */
let screenTimer = null, screenBusy = false, screenOn = false;
let screenObjUrl = null, screenFps = 1;
let screenLastMs = 0, screenMeasFps = 0; // 实测帧率(帧间间隔 EMA)
function stopScreenPoll() {
if (screenTimer) { clearInterval(screenTimer); screenTimer = null; }
screenOn = false;
if (screenTimer) { clearTimeout(screenTimer); screenTimer = null; }
}
function restartScreenPoll() {
if (screenTimer) { clearInterval(screenTimer); screenTimer = null; }
const img = $('#screen-img'); if (!img) return;
screenOn = true;
const ms = Math.max(500, Math.round(1000 / screenFps));
screenTimer = setInterval(fetchScreen, ms);
if (screenTimer) { clearTimeout(screenTimer); screenTimer = null; }
// 请求在途时不重复启动(避免并发请求);在途帧结束后会按最新 screenFps 自续
if (!screenBusy) screenTimer = setTimeout(fetchScreen, 0);
}
async function fetchScreen() {
if (screenBusy) return;
screenBusy = true;
const t0 = performance.now();
try {
const headers = {};
const token = storageGet('meowpanel.token');
if (token) headers['X-MeowToken'] = token;
const res = await fetch('/api/screen.jpeg?t=' + Date.now() + '&q=70',
Object.keys(headers).length ? { headers: headers } : undefined);
if (!res.ok) { $('#screen-status').textContent = '获取失败(HTTP ' + res.status + ')'; screenBusy = false; return; }
if (!res.ok) { $('#screen-status').textContent = '获取失败(HTTP ' + res.status + ')'; return; }
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const img = $('#screen-img');
if (!img) { URL.revokeObjectURL(url); screenBusy = false; return; }
if (!img) { URL.revokeObjectURL(url); return; }
img.src = url;
if (screenObjUrl) URL.revokeObjectURL(screenObjUrl);
screenObjUrl = url;
$('#screen-status').textContent = '已更新 · ' + new Date().toLocaleTimeString();
// 实测帧率:上一帧到本帧间隔的 EMA(首帧无间隔)
const now = performance.now();
if (screenLastMs) {
const dt = (now - screenLastMs) / 1000;
screenMeasFps = screenMeasFps ? screenMeasFps * 0.7 + (1 / dt) * 0.3 : 1 / dt;
}
screenLastMs = now;
$('#screen-status').textContent = '已更新' + (screenMeasFps ? ' · ' + screenMeasFps.toFixed(1) + ' fps' : '') + ' · ' + new Date().toLocaleTimeString();
} catch (e) {
$('#screen-status').textContent = '后端未连接';
}
screenBusy = false;
// 自校正续链(本轮耗时计入周期,保证名义 fps)
if (screenOn) {
const intervalMs = Math.max(500, Math.round(1000 / screenFps));
const wait = Math.max(0, intervalMs - (performance.now() - t0));
screenTimer = setTimeout(fetchScreen, wait);
}
}
async function screenTap(e) {
const img = $('#screen-img'); if (!img) return;
@@ -882,8 +899,8 @@
restartScreenPoll();
});
$('#screen-img').addEventListener('click', screenTap);
// restartScreenPoll 已排程首帧(上一帧到了再取下一帧),勿再显式 fetch 以免双发
restartScreenPoll();
fetchScreen();
}
function bindScreen() {
// 页面隐藏(切到其它标签页)时停刷;回到本页继续