fix(ncm): 三连修 — 封面切歌不更新/缓冲无反馈失败无声/暂停切歌边界 (REPORT §18)

- cover worker 忙等死循环(成功后不清目标,每圈解码+换入+gen++,CPU 100%+每帧整盘重绘)
  → 成功清目标转怠速; 失败指数退避 0.5-4s 连 8 次放弃+WARN 日志; 去重加在拉目标;
  cover_get 持锁+cover_put 修绘制期 UAF; g_host_mock 防真网守卫
- 封面兜底污染修复: 不再把单曲 al_pic 写进 g_st.cover_url(否则 al_pic 缺失曲回落成
  上一首封面且撞去重 → 封面永停); plr_page_enter 强制重估自愈; pl_page_enter 回填歌单封面
- 缓冲 UI: BUFFER 态「缓冲中… N%」(g_play.dl_done/total, 下载回调累加); 播放错误中文
  错误条+snack(旧版 err 无 UI 消费者=无声死等); STOPPED 态播放键=重试; play/next 清 err
- 写失败 set_err 修连环跳歌(旧版静默 break 被外层当自然播完,流死时跳完歌单+狂发 song_url);
  still_current 护栏修下载途中切歌的状态/快照过期覆盖
- 验证(0 真实请求): cover_chain_sim 7 组+player_flow_sim P1-P5 全 PASS, 旧代码对照
  T2/P2/P4 必败坐实根因; audiod_chain_sim 14/14; pagination/window/core+qemu 回归 PASS;
  宿主+mipsel 自有代码 0 warning; 9 页离屏渲染 rc=0 (含新 mock 页 playerbuf/playererr)
- ncm md5 50d41bbf4a379e925699ba013b997ffd (未部署真机, 留主会话)
This commit is contained in:
MeowOS committed 2026-09-01 05:29:09 +08:00
1 parent 614694ad4a
commit 6200a41b71
13 files changed
+820 -20

No files matched your search

+63 -7
View File
@@ -25,6 +25,7 @@ static ncm_rect_t r_cont;
static int g_custom_disc, g_custom_dyn;
static int g_landscape;
static int g_last_pos_ms, g_last_state, g_last_line, g_last_song;
static int g_last_dl, g_last_err; /* 缓冲进度粒度值 / 错误沿(§18) */
static char g_last_meta_id[24]; /* 已上封面的元数据 id(快照滞后 settle 时纠正封面/歌名) */
/* 运行期几何(player_compute_geom 填充;各带绘制物/命中共用)。 */
@@ -155,7 +156,7 @@ static void plr_draw_disc(void *ctx)
}
}
/* 封面:方盖内切于圆 + 四角盖回盘色 → 封面成圆。 */
/* 封面:方盖内切于圆 + 四角盖回盘色 → 封面成圆。(cover_get 持锁,绘完 cover_put,§18) */
meow_img *cov = cover_get();
int hS = S / 2;
if (cov) meow_ui_img_cover(cx - hS, cy - hS, S, cov);
@@ -167,6 +168,7 @@ static void plr_draw_disc(void *ctx)
}
meow_ui_text_center(cx, cy - 10, "\u266a", th->font_body, pa->red);
}
cover_put();
for (int dy = -R; dy <= R; dy++) {
int half = (int)sqrt((double)(R*R - dy*dy));
if (half <= hS) continue;
@@ -232,7 +234,9 @@ static void plr_draw_dyn(void *ctx)
int st = player_manager_state();
draw_ctl(g_cprev_x, g_ctl_y, g_cr - 6, 0, th->surfaceContainer, th->onSurface);
draw_ctl(g_cplay_x, g_ctl_y, g_cr, (st == NCM_PC_PAUSED) ? 1 : 2, pa->red, pa->on_red);
/* 暂停/停止(出错待重试)都画「播放」三角;播放/缓冲画暂停符 */
draw_ctl(g_cplay_x, g_ctl_y, g_cr,
(st == NCM_PC_PAUSED || st == NCM_PC_STOPPED) ? 1 : 2, pa->red, pa->on_red);
draw_ctl(g_cnext_x, g_ctl_y, g_cr - 6, 3, th->surfaceContainer, th->onSurface);
/* 歌词区(分隔线 + 滚动歌词,当前行红高亮居中) */
@@ -241,7 +245,32 @@ static void plr_draw_dyn(void *ctx)
int line_h = (g_landscape) ? 24 : 26;
int center_y = g_lyr.y + g_lyr.h / 2;
int lcx = g_lyr.x + g_lyr.w / 2;
if (n == 0) {
int perr = player_manager_error();
if (perr != 0) {
/* §18:播放错误明示(旧版 err 无 UI 消费者 → 失败无声死等) */
const char *em = (perr == NCM_ERR_NOTFOUND) ? "无法播放:无音源(VIP/版权)"
: (perr == NCM_ERR_AUTH) ? "登录已过期,请重新登录"
: "加载失败,请检查网络";
meow_ui_text_center(lcx, center_y - 20, em, 20, pa->red);
meow_ui_text_center(lcx, center_y + 10, "点播放键重试 / 切下一首", 20, th->onSurfaceVariant);
}
else if (st == NCM_PC_BUFFER) {
/* §18:缓冲反馈(song_url 获取 + 下载全程不再无提示) */
int64_t dd = 0, dt = 0;
player_manager_dl(&dd, &dt);
char bbuf[48];
if (dt > 0) {
int pct = (int)(dd * 100 / dt);
if (pct > 100) pct = 100;
snprintf(bbuf, sizeof bbuf, "缓冲中… %d%%", pct);
} else if (dd > 0) {
snprintf(bbuf, sizeof bbuf, "缓冲中… %lldKB", (long long)(dd / 1024));
} else {
snprintf(bbuf, sizeof bbuf, "缓冲中…");
}
meow_ui_text_center(lcx, center_y - 10, bbuf, 20, th->onSurfaceVariant);
}
else if (n == 0) {
/* 二期:歌词后台加载,状态区分「加载中 / 真的无词」 */
meow_ui_text_center(lcx, center_y - 10,
lyric_state() == 2 ? "暂无歌词" : "歌词加载中…",
@@ -293,8 +322,10 @@ void player_on_tap(int x, int y)
if (ah >= 0) { player_set_screen(!g_landscape); return; }
}
if (x >= g_cplay_x - g_cr && x <= g_cplay_x + g_cr && y >= g_ctl_y - g_cr && y <= g_ctl_y + g_cr) {
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
else player_manager_pause(1);
int st = player_manager_state();
if (st == NCM_PC_PAUSED) player_manager_pause(0);
else if (st == NCM_PC_STOPPED && cur_song() >= 0) player_manager_play(cur_song()); /* 出错后点播放=重试(§18) */
else player_manager_pause(1);
return;
}
if (x >= g_cprev_x - CTL_RR && x <= g_cprev_x + CTL_RR && y >= g_ctl_y - CTL_RR && y <= g_ctl_y + CTL_RR) {
@@ -314,12 +345,15 @@ void player_play_song(int idx)
{
if (idx < 0 || idx >= g_st.track_ids_count) return;
/* 黑胶封面用真实专辑图(song_detail 的 al.picUrl,经 ncm_cache 缓存/限幅);
* 滑窗未物化(占位行直点)时回落歌单封面,元数据由播放线程物化后快照接管(§17)。 */
* 滑窗未物化(占位行直点)时回落歌单封面,元数据由播放线程物化后快照接管(§17)。
* §18:g_st.cover_url 只作「歌单封面兜底」——不再把本曲 al_pic 写进去
* (旧写法污染兜底值:al_pic 缺失的歌会回落成**上一首的封面**,且撞上
* cover_request 去重 → 封面永远停在上一首,切歌不更新的确定性子根因)。 */
ncm_song_t s;
const char *cover = (appstate_song_copy(idx, &s) == 0 && s.al_pic[0])
? s.al_pic : g_st.cover_url;
player_manager_play(idx);
appstate_set_playing_song(idx, cover);
appstate_set_playing_song(idx, NULL);
cover_request(cover);
meow_view_page_push(ACT_PLAYER);
}
@@ -375,6 +409,22 @@ void player_tick(uint64_t now)
g_last_line = line;
meow_ui_invalidate(g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h); /* 歌词 */
}
/* §18:缓冲进度前进(~2KB 粒度)刷动态带(「缓冲中… N%」);仅缓冲态需要。 */
int64_t dd = 0, dtot = 0;
player_manager_dl(&dd, &dtot);
int dq = (int)(dd / 2048);
if (dq != g_last_dl) {
g_last_dl = dq;
if (st == NCM_PC_BUFFER)
meow_ui_invalidate(g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h);
}
/* §18:错误沿——错误条出现/消失刷动态带 + 新错误弹 snack(旧版 err 无任何 UI 出口)。 */
int perr = player_manager_error();
if (perr != g_last_err) {
g_last_err = perr;
meow_ui_invalidate(g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h);
if (perr) ncm_snack("播放失败");
}
}
static void plr_page_create(meow_view *v, void *ctx)
@@ -390,6 +440,7 @@ static void plr_page_create(meow_view *v, void *ctx)
g_custom_dyn = meow_draw_custom_register(11, g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h,
plr_draw_dyn, NULL);
g_last_pos_ms = -1; g_last_state = -1; g_last_line = -1; g_last_song = -1;
g_last_dl = -1; g_last_err = 0;
g_last_meta_id[0] = 0;
}
@@ -399,6 +450,11 @@ static void plr_page_enter(meow_view *v, void *ctx)
((player_ctrl *)ctx)->active = 1;
const ncm_pal_t *pa = ncm_palette(g_dark);
meow_ui_set_page_bg(pa->player_bg); /* 播放页恒沉浸,不随系统明暗整体反转 */
/* §18 自愈:重进播放页强制重估(g_last_meta_id 只在 create 清过一次——离开期间
* 切歌/封面失败后回来没有重评估路径;清零后首个 tick 按当前快照重请求封面+刷歌名)。 */
g_last_meta_id[0] = 0;
g_last_song = -1;
g_last_dl = -1;
meow_ui_paint_full();
if (!g_host_mock) player_manager_load_lyric();
}
+7 -1
View File
@@ -99,13 +99,14 @@ static void pl_draw(void *ctx)
/* 封面头块卡片 */
ncm_fill_round(r_card.x, r_card.y, r_card.w, r_card.h, 16, th->surfaceContainer);
meow_img *cov = cover_get();
meow_img *cov = cover_get(); /* 持锁返回,绘完 cover_put(§18) */
ncm_fill_round(r_cov.x, r_cov.y, r_cov.w, r_cov.h, 12, pa->red_ctr);
if (cov) meow_ui_img_cover(r_cov.x, r_cov.y, r_cov.w, cov);
else {
meow_ui_text_center(r_cov.x + r_cov.w / 2, r_cov.y + (r_cov.h - th->font_body) / 2,
"\u266a", th->font_body, pa->red); /* ♪ */
}
cover_put();
const ncm_playlist_t *pl = (g_st.active_playlist >= 0 && g_st.active_playlist < g_st.playlist_count)
? &g_st.playlists[g_st.active_playlist] : NULL;
const char *nm = pl ? pl->name : "歌单名";
@@ -418,6 +419,11 @@ static void pl_page_enter(meow_view *v, void *ctx)
((playlist_ctrl *)ctx)->active = 1;
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
/* §18:头块封面回填——封面槽全局单张(与播放页黑胶共享),进歌单页重新请求歌单封面
* (旧版只在 playlist_open 里 cover_cache_reset 清掉,无人再请求 → 头块永远占位 ♪)。 */
if (g_st.active_playlist >= 0 && g_st.active_playlist < g_st.playlist_count &&
g_st.playlists[g_st.active_playlist].cover[0])
cover_request(g_st.playlists[g_st.active_playlist].cover);
playlist_refresh();
}