Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/ui/player.c
T
MeowOS 6200a41b71 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 (未部署真机, 留主会话)
2026-09-01 05:29:09 +08:00

486 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ui/player.c — 播放页(ACT_PLAYER):黑胶唱盘 + 唱针 + 进度/控制/滚动歌词(竖/横布局可切)。
*
* 黑胶(NCM_UI_DESIGN §5.1,软渲无圆形原语,全 fill_rect 行扫):
* ① 盘体 = 对每行 dy 取 half=√(R²-dy²) 画 fill_rect(同心圆)。
* ② 封面粉圆 = 方盖(S=R×√2)内切于圆 + 四角把「圆外-方外」两段盖回盘色 = 封面成圆。
* ③ 唱针 = 从盘心右上到盘缘一根 3px 唱臂 + 端头;播放压盘/暂停微抬(状态切换才画)。
* 盘恒黑(palette.disc),accent(进度已播/播放钮/歌词高亮)日夜换红明暗(palette.red/red_lit)。
*
* 性能(§6,专项):**分带刷新**——内容拆 盘带(仅圆盘) + 动态带(歌名/进度/控制/歌词/页脚) 两个绘制物,
* 各带独立且互不重叠 bbox。tick 只标脏「变化的那一条带」,盘带仅 歌曲/封面/播停态 变化才重画
* (修 player_tick 每 tick 刷整 r_cont 的隐患)。播放页恒沉浸(day 瓷白 / night 近黑,
* 不随系统明暗整体反转,仅 accent 换红明暗,§5.4)。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
typedef struct { meow_view *view; int active; } player_ctrl;
static player_ctrl g_plc;
static meow_widget *g_tb;
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 填充;各带绘制物/命中共用)。 */
static ncm_rect_t g_disc_r, g_dyn_r; /* 盘带 / 动态带 bbox(不重叠) */
static int g_cx, g_cy, g_R, g_S; /* 盘心 / 半径 / 封面边长 */
static ncm_rect_t g_bar; /* 进度条 */
static int g_times_y;
static int g_ctl_y, g_cprev_x, g_cplay_x, g_cnext_x, g_cr;
static int g_title_y, g_artist_y, g_info_x, g_info_w;
static ncm_rect_t g_lyr; /* 歌词区 */
#define CTL_RR 30 /* 侧钮命中半径(命中容差) */
static int cur_song(void)
{
int c = player_manager_playing_song();
return (c >= 0 && c < g_st.track_ids_count) ? c : -1;
}
/* 当前播放曲元数据(§17):优先播放快照(窗口无关);快照未就绪时退窗口拷贝。 */
static int cur_song_meta(ncm_song_t *out)
{
if (player_manager_current_song(out) == 0) return 0;
int sc = cur_song();
if (sc >= 0 && appstate_song_copy(sc, out) == 0) return 0;
return -1;
}
static void fmt_ms(int64_t ms, char *out, size_t sz)
{
if (ms < 0) ms = 0;
int sec = (int)(ms / 1000);
snprintf(out, sz, "%d:%02d", sec / 60, sec % 60);
}
static void tri(int x0, int y0, int h, int w, int dir, uint32_t color)
{
for (int i = 0; i < h; i++) {
int wi = 1 + i * w / h;
if (dir == 1) meow_ui_fill_rect(x0, y0 + i, wi, 1, color);
else meow_ui_fill_rect(x0 - wi + 1, y0 + i, wi, 1, color);
}
}
static void draw_ctl(int cx, int cy, int r, int kind, uint32_t base, uint32_t fg)
{
for (int i = 0; i < 2 * r; i++) {
int dy = i - r;
int half = (int)sqrt((double)(r * r - dy * dy));
if (half <= 0) continue;
meow_ui_fill_rect(cx - half, cy - r + i, 2 * half, 1, base);
}
int t = r / 2;
if (kind == 0) { meow_ui_fill_rect(cx - r - 2, cy - t, 4, 2*t, fg);
tri(cx + r - 4, cy - t, 2*t, 2*t, -1, fg); }
else if (kind == 1) { tri(cx - t, cy - t, 2*t, 2*t, 1, fg); }
else if (kind == 2) { meow_ui_fill_rect(cx - t - 2, cy - t, 4, 2*t, fg);
meow_ui_fill_rect(cx + t - 2, cy - t, 4, 2*t, fg); }
else { tri(cx - r + 4, cy - t, 2*t, 2*t, 1, fg);
meow_ui_fill_rect(cx + r - 2, cy - t, 4, 2*t, fg); }
}
static void arm_line(int x0, int y0, int x1, int y1, uint32_t color, int wpx)
{
int dx = x1 - x0, dy = y1 - y0;
int steps = (abs(dx) > abs(dy) ? abs(dx) : abs(dy)) / 2 + 1;
if (steps < 1) steps = 1;
for (int i = 0; i <= steps; i++) {
int x = x0 + dx * i / steps;
int y = y0 + dy * i / steps;
meow_ui_fill_rect(x - wpx / 2, y - wpx / 2, wpx, wpx, color);
}
}
/* 几何(竖/横布局)。带 bbox:盘带=圆盘矩形,动态带=其余(互不重叠)。 */
static void player_compute_geom(void)
{
r_cont = ncm_content_rect();
if (g_landscape) {
g_cx = 180; g_cy = 360; g_R = 120; g_S = (int)(g_R * 1.414);
g_info_x = 340; g_info_w = r_cont.x + r_cont.w - 16 - g_info_x;
g_title_y = 150; g_artist_y = 188;
g_bar = NR(g_info_x, 240, 108, 4); g_times_y = 226;
g_ctl_y = 310; g_cprev_x = 348; g_cplay_x = 404; g_cnext_x = 460; g_cr = 26;
g_lyr = NR(300, 372, 164, 280);
} else {
g_cx = 240; g_cy = 272; g_R = 104; g_S = (int)(g_R * 1.414);
g_info_x = 240; g_info_w = r_cont.w;
g_title_y = 388; g_artist_y = 422;
g_bar = NR(56, 456, 368, 4); g_times_y = 436;
g_ctl_y = 508; g_cprev_x = 176; g_cplay_x = 240; g_cnext_x = 304; g_cr = 30;
g_lyr = NR(16, 546, 448, 150);
}
int cb = r_cont.y + r_cont.h - APP_FOOTER_H;
if (g_lyr.y + g_lyr.h > cb) g_lyr.h = cb - g_lyr.y;
if (g_lyr.h < 0) g_lyr.h = 0;
if (g_landscape) {
g_disc_r = NR(g_cx - g_R, g_cy - g_R, 2*g_R, 2*g_R); /* 左盘区 */
int dy0 = g_title_y - 12;
g_dyn_r = NR(g_info_x - 20, dy0, r_cont.x + r_cont.w - (g_info_x - 20), cb - dy0 + 8);
} else {
g_disc_r = NR(0, g_cy - g_R, r_cont.w, 2*g_R); /* 全宽圆盘带 */
g_dyn_r = NR(0, g_cy + g_R, r_cont.w, cb - (g_cy + g_R)); /* 其余(含歌名/进度/歌词) */
}
}
/* 盘带绘制物:盘体 + 方盖内切圆(四角盖色) + 封面 + 唱针。仅 歌曲/封面/播停态 见变化才重画。 */
static void plr_draw_disc(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_PLAYER) return;
const meow_theme *th = meow_theme_get();
const ncm_pal_t *pa = ncm_palette(g_dark);
int cx = g_cx, cy = g_cy, R = g_R, S = g_S;
int st = player_manager_state();
int paused = (st == NCM_PC_PAUSED);
for (int dy = -R; dy <= R; dy++) {
int half = (int)sqrt((double)(R*R - dy*dy));
if (half <= 0) continue;
meow_ui_fill_rect(cx - half, cy + dy, 2*half, 1, pa->disc);
}
for (int rr = R - 3; rr >= R - 12; rr -= 4) {
for (int dy = -rr; dy <= rr; dy++) {
int half = (int)sqrt((double)(rr*rr - dy*dy));
if (half <= 0) continue;
meow_ui_fill_rect(cx - half, cy + dy, 2*half, 1, pa->disc_edge);
}
}
/* 封面:方盖内切于圆 + 四角盖回盘色 → 封面成圆。(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);
else {
for (int dy = -hS; dy <= hS; dy++) {
int half = (int)sqrt((double)(hS*hS - dy*dy));
if (half <= 0) continue;
meow_ui_fill_rect(cx - half, cy + dy, 2*half, 1, pa->red_ctr);
}
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;
meow_ui_fill_rect(cx - half, cy + dy, half - hS, 1, pa->disc);
meow_ui_fill_rect(cx + hS, cy + dy, half - hS + 1, 1, pa->disc);
}
meow_ui_fill_rect(cx - 2, cy - 2, 4, 4, pa->player_bg); /* 盘心孔 */
/* 唱针:播放压盘 / 暂停微抬 6px。 */
int arm_top = paused ? 6 : 0;
int ax0 = cx + 40, ay0 = cy - 70 + arm_top;
int ax1 = cx + R - 6, ay1 = cy + 8 + arm_top;
arm_line(ax0, ay0, ax1, ay1, th->outline, 3);
meow_ui_fill_rect(ax1 - 3, ay1 - 3, 6, 6, th->onSurface);
/* 高光(廉价):左下 45° 1px 弧。 */
for (int a = 0; a < 20; a++) {
double t = (a / 19.0) * (3.14159 / 2);
int hx = cx - (int)((R - 6) * 0.71 * cos(t));
int hy = cy + (int)((R - 6) * 0.71 * sin(t));
meow_ui_fill_rect_alpha(hx, hy, 2, 1, 0xFFFFFF, 60);
}
}
/* 动态带绘制物:歌名/歌手 + 进度条/时间 + 控制钮 + 滚动歌词 + 页脚。tick 主要刷此带。 */
static void plr_draw_dyn(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_PLAYER) return;
const meow_theme *th = meow_theme_get();
const ncm_pal_t *pa = ncm_palette(g_dark);
ncm_song_t sm;
int have = (cur_song_meta(&sm) == 0);
const char *nm = have ? sm.name : "暂无播放";
const char *ar = have ? sm.art_name : "";
if (g_landscape) {
meow_ui_text(g_info_x, g_title_y, ncm_text_fit(nm, g_info_w, 28), 28, th->onSurface);
meow_ui_text(g_info_x, g_artist_y, ncm_text_fit(ar, g_info_w, 20), 20, th->onSurfaceVariant);
} else {
meow_ui_text_center(g_info_x, g_title_y, nm, 28, th->onSurface);
meow_ui_text_center(g_info_x, g_artist_y, ar, 20, th->onSurfaceVariant);
}
/* 进度条 + 时间 */
int pos = (int)player_manager_pos_ms();
int dur = (int)player_manager_dur_ms();
if (dur <= 0 && have) dur = (int)sm.duration_ms;
int frac = dur > 0 ? (int)((int64_t)pos * 1000 / dur) : 0;
if (frac < 0) frac = 0; if (frac > 1000) frac = 1000;
char lp[16], rp[16];
fmt_ms(pos, lp, sizeof lp);
fmt_ms(dur, rp, sizeof rp);
if (g_landscape) {
meow_ui_text(g_bar.x, g_times_y, lp, 20, th->onSurfaceVariant);
meow_ui_text_right(g_bar.x + g_bar.w, g_times_y, rp, 20, th->onSurfaceVariant);
} else {
meow_ui_text(44, g_times_y, lp, 20, th->onSurfaceVariant);
meow_ui_text_right(r_cont.x + r_cont.w - 44, g_times_y, rp, 20, th->onSurfaceVariant);
}
meow_ui_fill_rect(g_bar.x, g_bar.y, g_bar.w, g_bar.h, th->surfaceVariant);
meow_ui_fill_rect(g_bar.x, g_bar.y, (int)((int64_t)g_bar.w * frac / 1000), g_bar.h, pa->red);
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 || 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);
/* 歌词区(分隔线 + 滚动歌词,当前行红高亮居中) */
meow_ui_fill_rect(g_lyr.x, g_lyr.y, g_lyr.w, 1, th->outlineVariant);
int n = lyric_count();
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;
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 ? "暂无歌词" : "歌词加载中…",
20, th->onSurfaceVariant);
}
else {
int64_t ps = player_manager_pos_ms();
int cur2 = lyric_line_at(ps);
int lw = g_lyr.w - 8; /* 歌词列宽(裁剪,防窄列溢出) */
for (int i = cur2 - 6; i <= cur2 + 5; i++) {
if (i < 0 || i >= n) continue;
int yy = center_y + (i - cur2) * line_h;
if (yy < g_lyr.y || yy + line_h > g_lyr.y + g_lyr.h) continue; /* 裁剪到歌词带(自定义绘制物不按 bbox 裁剪) */
int is_cur = (i == cur2);
meow_ui_text_center(lcx, yy, ncm_text_fit(lyric_line_text(i), lw, is_cur ? 24 : 20),
is_cur ? 24 : 20, is_cur ? pa->red : th->onSurfaceVariant);
const char *tl = lyric_line_tl(i);
if (is_cur && tl && tl[0])
meow_ui_text_center(lcx, yy + 24, ncm_text_fit(tl, lw, 20), 20, th->secondary);
}
}
/* 页脚提示(用「・」U+30FB 而非 U+00B7「·」:common 字库按 GB2312 解码 A1A4 得 U+30FB,
* 不含 U+00B7 → 后者在设备上是豆腐块。REPORT §11 二期。) */
char foot[64];
snprintf(foot, sizeof foot, "左缘右滑返回 ・ ⟳ 切横屏");
meow_ui_text_center(r_cont.x + r_cont.w / 2, r_cont.y + r_cont.h - APP_FOOTER_H,
foot, 20, th->onSurfaceVariant);
}
int player_is_active(void) { return g_plc.active; }
int player_screen(void) { return g_landscape; }
int player_set_screen(int landscape)
{
int old = g_landscape;
g_landscape = landscape ? 1 : 0;
player_compute_geom();
meow_ui_invalidate(g_disc_r.x, g_disc_r.y, g_disc_r.w, g_disc_r.h);
meow_ui_invalidate(g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h);
return old;
}
void player_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return; }
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) {
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) {
player_manager_next(-1); return;
}
if (x >= g_cnext_x - CTL_RR && x <= g_cnext_x + CTL_RR && y >= g_ctl_y - CTL_RR && y <= g_ctl_y + CTL_RR) {
player_manager_next(1); return;
}
if (y >= g_bar.y - 10 && y <= g_bar.y + g_bar.h + 10 && x >= g_bar.x && x <= g_bar.x + g_bar.w) {
int64_t dur = player_manager_dur_ms();
if (dur > 0) player_seek((int64_t)(x - g_bar.x) * dur / g_bar.w);
return;
}
}
void player_play_song(int idx)
{
if (idx < 0 || idx >= g_st.track_ids_count) return;
/* 黑胶封面用真实专辑图(song_detail 的 al.picUrl,经 ncm_cache 缓存/限幅);
* 滑窗未物化(占位行直点)时回落歌单封面,元数据由播放线程物化后快照接管(§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, NULL);
cover_request(cover);
meow_view_page_push(ACT_PLAYER);
}
void player_pause_toggle(int on)
{
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
else player_manager_pause(1);
(void)on;
}
void player_next(int dir) { player_manager_next(dir); }
void player_seek(int64_t ms) { player_manager_seek(ms); }
void player_on_swipe(int dir, int dist) { (void)dir; (void)dist; }
/* tick:分带刷新——只标脏变化的那一带(§6)。盘带仅在歌曲/封面/播停态变化时重画。 */
void player_tick(uint64_t now)
{
(void)now;
chrome_tick();
if (!g_plc.active) return;
int sc = cur_song();
int pos = (int)player_manager_pos_ms();
int st = player_manager_state();
int line = (lyric_count() > 0) ? lyric_line_at(pos) : -1;
if (sc != g_last_song) {
g_last_song = sc;
meow_ui_invalidate(g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h); /* 歌名/歌手变 */
}
/* 元数据 id 变化(含快照滞后 settle:切歌瞬间快照仍是旧曲,play 线程更新后此处纠正)→
* 请求新曲专辑封面(黑胶盘换图)+ 刷动态带。 */
ncm_song_t s2;
if (sc >= 0 && cur_song_meta(&s2) == 0 && strcmp(s2.id, g_last_meta_id) != 0) {
ncm_strlcpy(g_last_meta_id, s2.id, sizeof g_last_meta_id);
cover_request(s2.al_pic[0] ? s2.al_pic : g_st.cover_url);
meow_ui_invalidate(g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h);
}
if (cover_changed()) {
cover_ack();
meow_ui_invalidate(g_disc_r.x, g_disc_r.y, g_disc_r.w, g_disc_r.h);
}
if (pos != g_last_pos_ms || st != g_last_state) {
g_last_pos_ms = pos;
int state_changed = (st != g_last_state);
g_last_state = st;
meow_ui_invalidate(g_dyn_r.x, g_dyn_r.y, g_dyn_r.w, g_dyn_r.h); /* 进度/控制钮/时间 */
if (state_changed)
meow_ui_invalidate(g_disc_r.x, g_disc_r.y, g_disc_r.w, g_disc_r.h); /* 唱针抬/压 */
}
if (line != g_last_line) {
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)
{
(void)v;
((player_ctrl *)ctx)->view = v;
player_compute_geom();
meow_ui_activity_push(ACT_PLAYER); /* owner=ACT_PLAYER */
g_tb = ncm_topbar_create("正在播放", 1, "\xe2\x86\xbb"); /* 返回 + ⟳ 布局 */
meow_ui_activity_pop();
g_custom_disc = meow_draw_custom_register(10, g_disc_r.x, g_disc_r.y, g_disc_r.w, g_disc_r.h,
plr_draw_disc, NULL);
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;
}
static void plr_page_enter(meow_view *v, void *ctx)
{
(void)v;
((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();
}
static void plr_page_leave(meow_view *v, void *ctx)
{
(void)v;
((player_ctrl *)ctx)->active = 0;
}
static void plr_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_custom_disc) { meow_draw_unregister(g_custom_disc); g_custom_disc = 0; }
if (g_custom_dyn) { meow_draw_unregister(g_custom_dyn); g_custom_dyn = 0; }
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
}
meow_view *player_view_create(void)
{
static const meow_view_hooks h = {
.on_create = plr_page_create,
.on_enter = plr_page_enter,
.on_leave = plr_page_leave,
.on_destroy = plr_page_destroy,
};
return meow_view_create(ACT_PLAYER, MEOW_VIEW_ACTIVITY, &h, &g_plc);
}