Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/now_playing.c
T

460 lines
24 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.
/* now_playing.c — 阶段D附录B:Now Playing 大页(主界面横划进入;左缘右滑返回)。
*
* 定案(用户):Now Playing = 通知中心媒体卡片 + 主界面横划进入的播放页,两者都做。
* 本文件实现后者:封面大图 + 元数据 + 进度 + 控制按钮 + 歌词占位;返回手势回主界面。
* 数据源:meow_media 会话(最近活跃媒体源);空态=占位。参照 SDK §5.2 横向分页模板的
* 手势分流(左缘返回 / 顶部下滑通知中心 / 中部横滑内容)——本页只接管「中部/右缘向左滑进、
* 左缘右滑返回」,与全局手势不冲突。
*
* 性能:封面解码缓存(按 artwork 路径变化重载,不每帧解码);整页 1s 刷新进度(墙钟推进),
* 元数据/源变化立即整页重画;交互只重绘按钮/进度带。
*/
#include "meow_launcher_internal.h"
#define NP_COVER 300 /* 封面边长(方,居中) */
#define NP_COVER_X ((fb_xres - NP_COVER) / 2)
#define NP_COVER_Y 60
#define NP_TITLE_Y 412
#define NP_ARTIST_Y 450
#define NP_ALBUM_Y 480
#define NP_PROG_X 40
#define NP_PROG_W (fb_xres - 80)
#define NP_PROG_Y 518
#define NP_TIME_Y 492
#define NP_BTN_Y 568 /* 控制按钮中心 y */
#define NP_PLAY_CX (fb_xres / 2)
#define NP_PREV_CX (NP_PLAY_CX - 78)
#define NP_NEXT_CX (NP_PLAY_CX + 78)
#define NP_PLAY_R 30
#define NP_PREV_R 22
#define NP_LYRIC_Y 640
static meow_img *g_np_cover = NULL;
static char g_np_cover_path[200] = "";
/* 封面解码缓存(artwork 路径变化才重载)。 */
static void np_cover_load(const meow_media_session_t *s)
{
if (!s) return;
if (strcmp(g_np_cover_path, s->artwork) == 0) return;
if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; }
snprintf(g_np_cover_path, sizeof g_np_cover_path, "%s", s->artwork);
if (!s->artwork[0]) return;
char path[200];
if (s->artwork[0] == '/') snprintf(path, sizeof path, "%s", s->artwork);
else snprintf(path, sizeof path, "/usr/data/mnt/sd_0/%s", s->artwork);
g_np_cover = meow_img_load_file(path);
}
#if 0 /* ================================================================
* M7.0b 退役:Now Playing 手绘控制钮/进度条改走 SDK 控件(meow_iconbtn +
* meow_progress),命中/状态/进度全由 SDK 接管;保留作回退位(删除 #if 0 行恢复旧手绘)。
* ================================================================ */
/* 实心三角(dir=+1 右向 / -1 左向)。 */
static void np_tri(int cx, int cy, int size, int dir, uint32_t col)
{
int half = size / 2;
for (int dy = -half; dy <= half; dy++) {
int w = half + 1 - (dy < 0 ? -dy : dy);
if (w <= 0) continue;
int x0 = cx + (dir > 0 ? -w / 2 : w / 2 - w);
fill_rect(x0, cy + dy, w, 1, col);
}
}
/* 圆形控制按钮(kind:0=prev 1=play 2=next 3=pause)。 */
static void np_btn(int cx, int cy, int r, int kind, const meow_ui_theme_t *th)
{
meow_gfx_fill_round(cx - r, cy - r, r * 2, r * 2, r, th->primary);
uint32_t fg = 0xFFFFFF;
if (kind == 0) {
np_tri(cx + 5, cy, r - 5, -1, fg);
fill_rect(cx - 4, cy - (r - 7), 4, (r - 7) * 2, fg);
} else if (kind == 1) {
np_tri(cx + 3, cy, r - 3, 1, fg);
} else if (kind == 2) {
np_tri(cx - 5, cy, r - 5, 1, fg);
fill_rect(cx + 2, cy - (r - 7), 4, (r - 7) * 2, fg);
} else {
int bw = (r - 8) / 2; if (bw < 2) bw = 2;
fill_rect(cx - 8, cy - (r - 7), bw, (r - 7) * 2, fg);
fill_rect(cx + 8 - bw, cy - (r - 7), bw, (r - 7) * 2, fg);
}
}
#endif /* M7.0b 退役:Now Playing 旧手绘控制钮 */
/* 居中 + 按宽截断(UTF-8 安全),防超长标题溢出屏外。(M7.0b 保留:封面/歌名/艺术家仍 SD) */
static void np_center_text(int y, const char *s, int size, uint32_t color, int maxw)
{
if (!s) s = "";
int W = fb_xres;
if (ui_width(s, size) <= maxw) {
ui_draw((W - ui_width(s, size)) / 2, y, s, color, size);
return;
}
/* 近似截断:实际按字节长度比例切(title/artist 罕见超长,够用不裂屏) */
char buf[160];
int keep = (int)(strlen(s) * maxw / (ui_width(s, size) + 1));
if (keep < 0) keep = 0;
if (keep > (int)sizeof buf - 4) keep = (int)sizeof buf - 4;
memcpy(buf, s, (size_t)keep); buf[keep] = 0;
strcat(buf, "...");
ui_draw((W - ui_width(buf, size)) / 2, y, buf, color, size);
}
/* ==================================================================
* M7.0b Now Playing 控件化(launcher 无 meow_ui_run,手动驱动 SDK 控件):
* - 进度条 → meow_progress(线性,SDK 主题 track/fill);
* - 播放控制三钮 → meow_iconbtn(PREV / PLAY_ARROW|PAUSE / NEXT;按下=PAUSE、暂停=PLAY_ARROW,
* 同位置叠两钮、按当前状态只画其一);
* - 命中走 meow_widget_hit(圆形,owner==哨兵 + enabled 闸门)→ meow_media_playctl 分发;
* - 封面(meow_img contain)与歌名/艺术家(meow_font)不动(本就 SD),仅文案走 meow_str。
* 控件 lazy 创建一次(owner=NP_SENTINEL_ACT),跨多次进入复用;run_now_playing() 进入 push
* 哨兵、退出 pop(owner 闸门使控件命中生效)。
* ================================================================== */
#define NP_SENTINEL_ACT 0x51 /* Now Playing 哨兵 activity */
static meow_widget *g_np_prog = NULL;
static meow_widget *g_np_prev = NULL, *g_np_play = NULL, *g_np_pause = NULL, *g_np_next = NULL;
static int g_np_init = 0;
static void np_widgets_create(void)
{
meow_ui_activity_push(NP_SENTINEL_ACT);
g_np_prog = meow_progress_create(NP_PROG_X, NP_PROG_Y, NP_PROG_W, 6, 0);
g_np_prev = meow_iconbtn_create(NP_PREV_CX - NP_PREV_R, NP_BTN_Y - NP_PREV_R,
NP_PREV_R * 2, MEOW_ICON_SKIP_PREV);
g_np_play = meow_iconbtn_create(NP_PLAY_CX - NP_PLAY_R, NP_BTN_Y - NP_PLAY_R,
NP_PLAY_R * 2, MEOW_ICON_PLAY_ARROW);
g_np_pause = meow_iconbtn_create(NP_PLAY_CX - NP_PLAY_R, NP_BTN_Y - NP_PLAY_R,
NP_PLAY_R * 2, MEOW_ICON_PAUSE);
g_np_next = meow_iconbtn_create(NP_NEXT_CX - NP_PREV_R, NP_BTN_Y - NP_PREV_R,
NP_PREV_R * 2, MEOW_ICON_SKIP_NEXT);
/* 主播放钮保持「强调动作」观感:底色=primary、图标=onPrimary;其余两钮默认 surfaceContainerLow。 */
const meow_theme *th = meow_theme_get();
meow_widget_opts op; memset(&op, 0, sizeof op);
op.bg = th->primary; op.fg = th->onPrimary;
if (g_np_play) meow_widget_set_opts(g_np_play, &op);
if (g_np_pause) meow_widget_set_opts(g_np_pause, &op);
meow_ui_activity_pop();
g_np_init = 1;
}
/* 主题刷新:播放钮底色/图标随当前主题(明暗/风格切换后跟手)。 */
static void np_refresh_theme(void)
{
if (!g_np_init) return;
const meow_theme *th = meow_theme_get();
meow_widget_opts op; memset(&op, 0, sizeof op);
op.bg = th->primary; op.fg = th->onPrimary;
if (g_np_play) meow_widget_set_opts(g_np_play, &op);
if (g_np_pause) meow_widget_set_opts(g_np_pause, &op);
}
/* 绘制播放控制三钮(prev/next 固定;play/pause 按会话状态只画其一——两钮同位,改画时
* 上一帧残留被新画圆底盖掉)。 */
static void np_draw_ctl(const meow_media_session_t *s)
{
np_refresh_theme();
if (g_np_prev) { meow_widget_set_selected(g_np_prev, 0); meow_widget_draw(g_np_prev); }
if (g_np_next) { meow_widget_set_selected(g_np_next, 0); meow_widget_draw(g_np_next); }
int playing = (s && s->state == MEOW_MEDIA_PLAYING);
meow_widget *pb = playing ? g_np_pause : g_np_play;
if (pb) { meow_widget_set_selected(pb, 0); meow_widget_draw(pb); }
}
/* 进度带(进度条 + 时间)绘制;pos = 当前显示位置。进度条走 SDK meow_progress。 */
static void np_draw_progress(const meow_media_session_t *s, uint32_t pos, const meow_ui_theme_t *th)
{
int pct = (s->dur_ms > 0) ? (int)((uint64_t)pos * 100 / s->dur_ms) : 0;
if (pct > 100) pct = 100;
if (g_np_prog) { meow_progress_set(g_np_prog, pct); meow_widget_draw(g_np_prog); }
/* 时间(左=当前,右=总长;MM:SS) */
char l[16], rbuf[16];
unsigned lm = pos / 60000u, ls = (pos % 60000u) / 1000u;
unsigned dm = (s->dur_ms ? s->dur_ms : 0) / 60000u, ds = (s->dur_ms ? s->dur_ms : 0) % 60000u / 1000u;
snprintf(l, sizeof l, "%u:%02u", lm, ls);
snprintf(rbuf, sizeof rbuf, "%u:%02u", dm, ds);
ui_draw(NP_PROG_X, NP_TIME_Y, l, th->on_surface_variant, 16);
ui_draw(NP_PROG_X + NP_PROG_W - ui_width(rbuf, 16), NP_TIME_Y, rbuf, th->on_surface_variant, 16);
}
/* 整页绘制(空态占位)。redraw_sb=1 时喂/标脏状态栏(打开/主题变化),否则保留上次顶条。 */
static void np_draw_page(int redraw_sb)
{
const meow_ui_theme_t *th = s_ui_theme();
int W = fb_xres, H = fb_yres;
if (!g_np_init) np_widgets_create(); /* M7.0b:lazy 建控件(跨进入复用) */
if (redraw_sb) fill_rect(0, 0, W, SB_BAR_H, th->surface);
fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface);
const meow_media_session_t *s = meow_media_current();
if (!s || s->state == MEOW_MEDIA_STOPPED) {
/* 空态占位(M7.0b 文案走 meow_str,未知 id 回退中文) */
const char *t1 = meow_str_zh("np.not_playing", "未在播放");
ui_draw((W - ui_width(t1, 26)) / 2, 300, t1, th->on_surface_variant, 26);
const char *t2 = meow_str_zh("np.open_app_hint", "打开播放 App 以显示正在播放");
ui_draw((W - ui_width(t2, 18)) / 2, 348, t2, th->on_surface_variant, 18);
const char *t3 = meow_str_zh("np.edge_back", "左缘右滑返回");
ui_draw((W - ui_width(t3, 16)) / 2, 396, t3, th->on_surface_variant, 16);
if (redraw_sb) draw_statusbar();
return;
}
np_cover_load(s);
/* 封面:解码缓存 + 圆角裁方;无封面回退主题色占位 + 首字 */
if (g_np_cover) {
meow_gfx_fill_round(NP_COVER_X - 6, NP_COVER_Y - 6, NP_COVER + 12, NP_COVER + 12, 18, th->surface_container);
meow_ui_img_cover(NP_COVER_X, NP_COVER_Y, NP_COVER, g_np_cover);
} else {
meow_gfx_fill_round(NP_COVER_X - 6, NP_COVER_Y - 6, NP_COVER + 12, NP_COVER + 12, 18, th->surface_container);
meow_gfx_fill_round(NP_COVER_X, NP_COVER_Y, NP_COVER, NP_COVER, 14, th->primary);
const char *t = s->title[0] ? s->title : s->app_id;
int cw = ui_width(t, 22);
ui_draw(NP_COVER_X + (NP_COVER - cw) / 2, NP_COVER_Y + (NP_COVER - meow_font_line_height(22)) / 2,
t, meow_theme_get()->onPrimary, 22);
}
/* 元数据 */
np_center_text(NP_TITLE_Y, s->title, 26, th->on_surface, W - 40);
np_center_text(NP_ARTIST_Y, s->artist, 18, th->on_surface_variant, W - 60);
if (s->album[0])
np_center_text(NP_ALBUM_Y, s->album, 16, th->on_surface_variant, W - 80);
/* 进度(SDK meow_progress) */
uint32_t pos = meow_media_display_pos(s);
np_draw_progress(s, pos, th);
/* 控制按钮(SDK meow_iconbtn,命中/状态走 SDK) */
np_draw_ctl(s);
/* 歌词占位(M7.0b 文案走 meow_str) */
const char *ly = meow_str_zh("np.lyric_placeholder", "歌词(占位)");
ui_draw((W - ui_width(ly, 16)) / 2, NP_LYRIC_Y, ly, th->on_surface_variant, 16);
if (redraw_sb) draw_statusbar();
}
/* 仅重绘进度带 + 按钮(元数据/源变化时整页;纯进度变化时局部)。 */
static void np_repaint_progress(void)
{
const meow_ui_theme_t *th = s_ui_theme();
const meow_media_session_t *s = meow_media_current();
if (!s || s->state == MEOW_MEDIA_STOPPED) return;
uint32_t pos = meow_media_display_pos(s);
/* 清进度带 + 控制带,再重绘(只脏这两带,不整屏)。 */
fill_rect(0, NP_TIME_Y, fb_xres, NP_BTN_Y + NP_PLAY_R - NP_TIME_Y, th->surface);
np_draw_progress(s, pos, th);
np_draw_ctl(s);
meow_dirty_add(0, NP_TIME_Y, fb_xres, NP_BTN_Y + NP_PLAY_R - NP_TIME_Y);
meow_ui_flush();
}
/* 控制按钮命中:返回 cmd(1-4),0=未命中。走 SDK 圆形命中(owner==哨兵 + enabled 闸门)。
* 播放/暂停两钮同位:命中任一(同矩形)→ 按会话状态分派 play(1)/pause(2)。 */
static int np_btn_tap(int tx, int ty)
{
const meow_media_session_t *s = meow_media_current();
if (g_np_prev && meow_widget_hit(g_np_prev, tx, ty)) return 3;
if (g_np_play && meow_widget_hit(g_np_play, tx, ty))
return (s && s->state == MEOW_MEDIA_PLAYING) ? 2 : 1;
if (g_np_next && meow_widget_hit(g_np_next, tx, ty)) return 4;
return 0;
}
/* 进度 seek 命中(拖动/点按进度带):返回 seek_ms(>0),0=未中(或 dur=0)。 */
static int64_t np_progress_seek(int tx, int ty)
{
const meow_media_session_t *s = meow_media_current();
if (!s || !s->dur_ms) return 0;
if (tx >= NP_PROG_X && tx <= NP_PROG_X + NP_PROG_W &&
ty >= NP_PROG_Y - 14 && ty <= NP_PROG_Y + 20) {
int frac = (tx - NP_PROG_X) * 1000 / NP_PROG_W;
return (int64_t)s->dur_ms * frac / 1000;
}
return 0;
}
void run_now_playing(struct app *apps, int napps)
{
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
if (tfd >= 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == 16) {} }
unsigned long long touch_cooldown_until = now_ms() + 300;
struct keypoll kp_keys;
kp_open(&kp_keys, DEV_KEYS);
struct keypoll kp_power; /* M2.92:NP 页接管电源键(修「页内电源键无反应」) */
kp_open(&kp_power, DEV_POWER);
struct kd_state kd_play;
memset(&kd_play, 0, sizeof kd_play);
/* 读播放键按住态(用于边沿) */
int m_prev_play = 0;
for (int w = 0; w < KEYBUF_WORDS; w++)
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) m_prev_play = 1;
int np_prev_power = 0; /* 电源键边沿跟踪 */
for (int w = 0; w < KEYBUF_WORDS; w++)
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) np_prev_power = 1;
unsigned long long np_last_input = now_ms(); /* 自动息屏计时(M2.92:NP 页也遵守 display.screen_off_timeout) */
int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1;
unsigned long long last_prog = now_ms();
unsigned long long np_last_status_tick = now_ms(); /* M6.20:播放页状态栏时钟 1s 刷新(主界面同语义) */
int need_redraw = 1;
int np_exit = 0; /* M6.22:返回手势命中 → 置位退出外层主循环。触摸内的 break 只跳出内层
* 读事件 while,不退出外层 while(!sigterm_flag),故必须靠标志跳出(根因:返回「没反应」)。 */
char last_title[128] = "";
uint32_t last_state = 0xFFFFFFFFu;
char last_app[MEOW_APP_ID_MAX] = "";
meow_ui_activity_push(NP_SENTINEL_ACT); /* M7.0b:进入 push 哨兵(与函数尾 449 行 pop 配对)。
* 控件 owner 一直钉哨兵,命中要走 meow_widget_hit 的
* owner==active 闸门——漏 push 则 active()!=0x51,
* 三钮 meow_widget_hit 恒 false(「播放页三键无效」根因)。 */
np_draw_page(1);
meow_dirty_full();
meow_ui_flush();
while (!sigterm_flag) {
if (np_exit) break; /* M6.22:返回手势已命中 → 退出本页,走尾部收尾回主界面 */
meow_ipc_process();
meow_media_tick();
unsigned long long now = now_ms();
/* 触摸 */
if (tfd >= 0 && meow_screen_on) {
struct pollfd pfd = { tfd, POLLIN, 0 };
if (poll(&pfd, 1, 0) > 0) {
unsigned char ev[16];
while (read(tfd, ev, sizeof ev) == 16) {
uint16_t type = *(uint16_t *)(ev + 8);
uint16_t code = *(uint16_t *)(ev + 10);
int32_t val = *(int32_t *)(ev + 12);
if (type == EV_ABS) {
if (code == ABS_X || code == ABS_MT_POSITION_X) tlx = val;
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) tly = val;
if (td && !td_ok && tlx >= 0 && tly >= 0) { tdx = tlx; tdy = tly; td_ok = 1; }
} else if (type == EV_KEY && code == BTN_TOUCH) {
if (val && !td) {
if (now_ms() >= touch_cooldown_until) { td = 1; td_ok = 0; tlx = tly = -1;
np_last_input = now_ms(); }
} else if (!val && td) {
td = 0;
if (td_ok) {
int dx = tlx - tdx, dy = tly - tdy;
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
/* M6.21 诊断:打印该次抬手手势的完整输入与各判定结果(真机定位
* 「返回滑不出」根因——状态栏/通知中心已生效,惟返回未触发)。 */
int np_is_top = mnp_top_swipe_down(tdx, tdy, dx, dy, SB_BAR_H, 65);
int np_edge = meow_edge_back(tdx, dx, dy);
int np_anyright = (dx >= 40 && adx > ady);
logmsg("np up: down=(%d,%d) d=(%d,%d) adx=%d ady=%d top=%d edge=%d anyright=%d",
tdx, tdy, dx, dy, adx, ady, np_is_top, np_edge, np_anyright);
/* 顶部热区下滑 → 打开通知中心(与主界面同语义;mnp_top_swipe_down
* 内部已排除左缘 down_x<24,与下方返回手势互斥)。run_notif_center
* 为阻塞模态,返回后整页重画播放页。 */
if (np_is_top) {
notif_click_t click;
memset(&click, 0, sizeof click);
click.app_idx = -1;
click.status = NOTIF_CLICK_STATUS_NONE;
run_notif_center(apps, napps, &click, 0, 0, 0);
need_redraw = 1;
meow_dirty_full();
meow_ui_flush();
}
/* 左缘右滑返回(全局手势语义),或任意横向右滑返回——与主界面
* 「左滑进入 Now Playing」对称。返回阈值 65→40 放宽(防用户右滑
* 幅度不足 65 判不中);播放页无横向分页内容、无进度条拖动
* (进度=点按,幅度<60 走 tap 分支),故任意位置横向右滑恒判返回。 */
else if (np_edge || np_anyright) {
logmsg("np: BACK break (edge=%d anyright=%d dx=%d)", np_edge, np_anyright, dx);
np_exit = 1;
break; /* 仅跳出内层读事件 while;外层主循环靠 np_exit 退出 */
}
if (adx < 60 && ady < 60) {
int cmd = np_btn_tap(tlx, tly);
if (cmd) { meow_media_playctl((uint32_t)cmd, 0); need_redraw = 1; }
else {
int64_t seek = np_progress_seek(tlx, tly);
if (seek > 0) meow_media_playctl(5, seek);
}
}
tdx = tdy = -1;
} else { tdx = tdy = -1; }
}
}
}
}
}
/* 播放键边沿 → 播放/暂停切换 */
kp_poll(&kp_keys, NULL, NULL);
int play = 0;
for (int w = 0; w < KEYBUF_WORDS; w++)
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) play = 1;
if (play && !m_prev_play) {
const meow_media_session_t *s = meow_media_current();
if (s) meow_media_playctl((s->state == MEOW_MEDIA_PLAYING) ? 2u : 1u, 0);
np_last_input = now_ms();
}
m_prev_play = play;
/* M2.92:NP 页电源键接管——单击边沿 = 息屏/亮屏(与主界面语义一致;
* 修「NP 页电源键无反应」。3s 电源菜单仍由 watchdog→/tmp 标记→主循环消费,
* NP 退出后生效(可接受偏差,记日志)。 */
kp_poll(&kp_power, NULL, NULL);
int pwr = 0;
for (int w = 0; w < KEYBUF_WORDS; w++)
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) pwr = 1;
if (pwr && !np_prev_power) {
np_last_input = now_ms();
if (meow_screen_on) { meow_panel_off(); logmsg("nowplaying: power tap -> panel off"); }
else { meow_panel_on(); need_redraw = 1; logmsg("nowplaying: power tap -> panel on"); }
}
np_prev_power = pwr;
/* M2.92:NP 页自动息屏(与主界面同超时;页面无触摸/按键计时器不走主循环) */
if (screen_off_timeout > 0 && meow_screen_on &&
now_ms() - np_last_input >= (unsigned long long)screen_off_timeout * 1000) {
meow_panel_off();
meow_auto_slept = 1;
logmsg("nowplaying: idle %ds -> auto panel off", screen_off_timeout);
}
/* 息屏期只低频轮询按键(唤醒用),不重画不读触摸(FBIOBLANK 已挂起 goodix) */
if (!meow_screen_on) { usleep(100 * 1000); continue; }
/* 媒体变化检测(元数据/源/状态):立即整页重画 */
const meow_media_session_t *s = meow_media_current();
int meta_changed = !s || strcmp(last_app, s->app_id) ||
strcmp(last_title, s->title) || last_state != s->state;
if (meta_changed) {
if (s) {
snprintf(last_app, sizeof last_app, "%s", s->app_id);
snprintf(last_title, sizeof last_title, "%s", s->title);
last_state = s->state;
} else { last_app[0] = 0; last_title[0] = 0; last_state = 0xFFFFFFFFu; }
need_redraw = 1;
}
/* M2.92 修死循环:空态/停止态只在「变化」时画一次(meta_changed 已覆盖
* NULL↔会话切换),不再每 10ms 整页重画(旧行为 = 100% CPU 忙刷 → 卡死观感)。 */
if (need_redraw) {
np_draw_page(0);
meow_dirty_add(0, SB_BAR_H, fb_xres, fb_yres - SB_BAR_H);
meow_ui_flush();
need_redraw = 0;
last_prog = now;
} else if (s && (now - last_prog >= 1000)) {
/* 进度推进:仅重绘进度带 + 按钮 */
last_prog = now;
np_repaint_progress();
}
/* M6.20:播放页状态栏时钟 1s 刷新(与主界面/子页同语义;draw_clock 喂 SDK 全局
* 状态栏 + 标脏顶条,SDK present() 重绘——否则时间冻结在进入播放页那一刻)。 */
if (now - np_last_status_tick >= 1000) {
np_last_status_tick = now;
draw_clock();
}
usleep(10000);
}
meow_ui_activity_pop(); /* M7.0b:退出 Now Playing,回主 activity(控件 owner 仍钉哨兵,复用) */
if (tfd >= 0) close(tfd);
kp_close(&kp_keys);
kp_close(&kp_power);
if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; }
g_np_cover_path[0] = 0;
}