feat: M6.10–M6.19 批量收编 + M6.19 推卡验收

- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
AskaEth committed 2026-09-03 12:16:01 +08:00
1 parent c2ee7626a4
commit 3d70a3dd2c
176 files changed
+15507 -597

No files matched your search

+8 -7
View File
@@ -88,14 +88,14 @@ void account_load(void)
int64_t last_refresh = ncm_json_get_i64_at(&j, ncm_json_path(&j, "last_refresh_ts"), &rc);
if (rc != NCM_OK) last_refresh = 0;
if (vip < NCM_VIP_NONE || vip > NCM_VIP_SVIP) vip = NCM_VIP_NONE;
if (vip < NCM_VIP_NONE || vip > NCM_VIP_SUPER) vip = NCM_VIP_NONE;
g_st.vip = (int)vip;
g_st.vip_cvip_exp_ms = cvip_exp;
g_st.vip_svip_exp_ms = svip_exp;
g_st.vip_checked_at = checked;
g_st.cookie_last_refresh_ts = last_refresh;
/* 有效到期时间 = 当前档对应的到期字段 */
g_st.vip_expire_ms = (vip == NCM_VIP_SVIP) ? svip_exp
/* 有效到期时间 = 当前档对应的到期字段(SUPER 无独立到期字段,沿黑胶 svip 场)。 */
g_st.vip_expire_ms = (vip == NCM_VIP_SVIP || vip == NCM_VIP_SUPER) ? svip_exp
: (vip == NCM_VIP_CVIP) ? cvip_exp : 0;
meow_log(MEOW_LOG_INFO, "account: loaded vip=%d expire=%lld last_refresh_ts=%lld",
g_st.vip, (long long)g_st.vip_expire_ms, (long long)last_refresh);
@@ -129,9 +129,10 @@ int account_vip(void)
int account_level_cap(void)
{
switch (account_vip()) {
case NCM_VIP_SVIP: return NCM_LEVEL_SURROUND;
case NCM_VIP_CVIP: return NCM_LEVEL_HIRES;
default: return NCM_LEVEL_EXHIGH;
case NCM_VIP_SVIP:
case NCM_VIP_SUPER: return NCM_LEVEL_SURROUND;
case NCM_VIP_CVIP: return NCM_LEVEL_HIRES;
default: return NCM_LEVEL_EXHIGH;
}
}
@@ -164,7 +165,7 @@ void account_vip_maybe_refresh(int force)
g_st.vip = info.vip;
g_st.vip_cvip_exp_ms = info.cvip_expire_ms;
g_st.vip_svip_exp_ms = info.svip_expire_ms;
g_st.vip_expire_ms = (info.vip == NCM_VIP_SVIP) ? info.svip_expire_ms
g_st.vip_expire_ms = (info.vip == NCM_VIP_SVIP || info.vip == NCM_VIP_SUPER) ? info.svip_expire_ms
: (info.vip == NCM_VIP_CVIP) ? info.cvip_expire_ms : 0;
g_st.vip_checked_at = now_s;
account_persist(info.vip, info.cvip_expire_ms, info.svip_expire_ms, now_s);
+31 -9
View File
@@ -278,8 +278,11 @@ static meow_img *download_cover(const char *url, ncm_cache_writer_t *w, size_t *
/* 封面原始文件路径写入(供 launcher 媒体卡片读;兼容保留)。调用方须已持有 g_cm。
* 从 cache 写者临时文件把已下载字节复制到 cover_current.jpg(早前版本在此内再 lock g_cm
* → 非递归锁自锁死——cover worker 永久卡死、封面永不出现)。
* §NEW 缓存分目录:与歌曲分池,cover_current.jpg 落 covers/(封面条目 song_cov* 也在 covers/)。 */
static void save_cover_raw(const char *path, size_t len)
* §NEW 缓存分目录:与歌曲分池,cover_current.jpg 落 covers/(封面条目 song_cov* 也在 covers/)。
* REPORT §封面混用:is_song=0(歌单封面,无曲目 id)时**只写 cover_current.jpg 镜像、不更新
* g_cover_path**——g_cover_path 是上报 launcher 通知中心媒体卡片的「当前播放曲」封面指针;
* 歌单页浏览加载歌单封面不应覆盖正在播放曲目的封面(用户真机:通知中心显示另一歌单封面)。 */
static void save_cover_raw(const char *path, size_t len, int is_song)
{
if (!path || len == 0) return;
const char *covdir = g_st.ctx.cache.covers_dir[0] ? g_st.ctx.cache.covers_dir
@@ -301,17 +304,20 @@ static void save_cover_raw(const char *path, size_t len)
}
fclose(in);
}
ncm_strlcpy(g_cover_path, dst, sizeof g_cover_path);
if (is_song) ncm_strlcpy(g_cover_path, dst, sizeof g_cover_path);
}
}
#define COVER_RETRY_MAX 8 /* 连续失败放弃次数(UI 重进页/切歌会重新提交目标) */
/* 内存瘦身:封面解码后按显示上限降采样驻留。网易云全尺寸封面(如 1448×1448)解码成
* RGBA = 8MB,而应用显示只需 ≤480px(播放页黑胶方图+歌词页模糊铺底均 ≤ 屏幕 480 宽)——
* 缩到 480×480 = 0.9MB,省 ~7MB anon(设备 RAM 仅 56MB,OOM 真机被杀,最高优先级)。
* 若已 ≤上限(小缩略图)则原样保留。返回本函数处理后的图(可能替换/释放旧图)。 */
#define NCM_COVER_DECODE_MAX 480
* RGBA = 8MB,而应用显示(歌词页 cover-fit 铺满 480×720 全屏,方图实际渲染到 720×720;封面页
* 方图仅 304×304)——请求 param=640y640(见 ncm_app_internal.h)即 ≤640 边长,故此上限须 ≥ 640
* 否则解码后又被降采样回 480、仍不够铺满 480 宽(480 图铺 480+ 显示照旧放大)。640×640 RGBA
* ≈ 1.6MB 单张驻留(设备 RAM 仅 56MB,OOM 真机被杀;比 308 瘦身 8.28MB 仍省 6.7MB,单张 1.6MB
* 在 32bit OOM 预算内——换封面仍先 free 旧再解码)。
* 若已 ≤上限(小缩略图/无 param 全尺寸在 640 内)则原样保留。返回本函数处理后的图(可能替换/释放旧图)。 */
#define NCM_COVER_DECODE_MAX 640
static meow_img *cover_cap(meow_img *im)
{
if (!im) return NULL;
@@ -362,7 +368,7 @@ static void *cover_worker(void *arg)
img = download_cover(url, w, &blen, &drc, &dstatus, &dkind);
if (img) {
pthread_mutex_lock(&g_cm);
save_cover_raw(ncm_cache_writer_tmp_path(w), blen);
save_cover_raw(ncm_cache_writer_tmp_path(w), blen, song[0] ? 1 : 0);
pthread_mutex_unlock(&g_cm);
if (ncm_cache_commit(w) != 0) { ncm_cache_abort(w); }
} else {
@@ -452,6 +458,21 @@ static void *cover_worker(void *arg)
if (g_cover_img) meow_img_free(g_cover_img);
g_cover_img = img;
ncm_strlcpy(g_cover_url, url, sizeof g_cover_url);
/* 上报给 launcher 的封面路径 = 本目标专辑的缓存条目(per-URL 唯一)。
* 旧码只在 save_cover_raw(下载路径)更新 g_cover_path;缓存命中路径从不更新 →
* cover_current_path() 恒空 → MSG_META artwork 恒空 → 通知中心媒体卡拿不到封面路径
* → 回退显示歌名。此处统一在「目标达成换入」时把 g_cover_path 指向已落盘的缓存条目
* (下载 commit 后 / 缓存命中时均真实存在);commit 失败则保留 save_cover_raw 已写的
* cover_current.jpg 镜像(仍在)。路径随专辑变化 → launcher 侧「按 artwork 文本缓存」
* 的现有实现能随切歌重载,不残留旧封面。 */
if (song[0] && g_st.ctx.cache.dir[0]) {
char cpath[512];
if (ncm_cache_path(&g_st.ctx.cache, key, ".jpg", cpath, sizeof cpath) == NCM_OK) {
struct stat cs;
if (stat(cpath, &cs) == 0 && cs.st_size > 0)
ncm_strlcpy(g_cover_path, cpath, sizeof g_cover_path);
}
}
g_gen++;
g_tgt_url[0] = 0; /* 目标达成即清:worker 转怠速(修忙等死循环,§18) */
} else {
@@ -464,7 +485,8 @@ static void *cover_worker(void *arg)
/* UI 线程:提交目标封面 URL(无网络/无 URL 则忽略)。song_id:曲目 id(专辑封面死链时走
* song/detail 兜底重取,见 REPORT §NEW 任务1);NULL/空=歌单封面(无兜底)。
* 先 cover_norm_url 规范化(追加 ?param=300y300 缩略后缀,REPORT §NEW 任务2),使
* 先 cover_norm_url 规范化(追加 ?param=640y640 缩略后缀,REPORT §NEW 任务2;640 适配歌词页
* cover-fit 铺满 480×720,见 ncm_app_internal.h),使
* g_tgt_url/g_cover_url/缓存键/下载 URL 走同一参数版本(小图省内存、命中高)。 */
void cover_request(const char *url, const char *song_id)
{
+41 -9
View File
@@ -50,6 +50,41 @@ static void copy_lrc_text(char *dst, const char *src, size_t n, size_t max)
dst[oi] = 0;
}
/* 解析单个 [mm:ss.ff] 时间标签为毫秒。返回 1=合法时间标签(*out_ms、*next=']' 之后);
* 0=非时间标签(*next=']' 之后;NULL=无闭合 ']',调用方按残缺处理)。
* 小数位自适应:1 位=0.1s(×100)、2 位=0.01s(×10,标准 LRC 厘秒)、3 位=0.001s(×1,
* 网易云 LRC 毫秒)——此前固定 ×10 把网易云 3 位毫秒当 2 位厘秒解析,时间戳放大 ~10 倍,
* 歌词行按绝对时间激活滞后 ~4~10s(REPORT「英文歌歌词滚动慢」)。 */
static int parse_lrc_tag(const char *p, long *out_ms, const char **next)
{
const char *q = p + 1; /* 跳过 '[' */
int m = 0, md = 0, s = 0, sd = 0;
while (*q >= '0' && *q <= '9') { m = m * 10 + (*q - '0'); md++; q++; }
if (md == 0 || *q != ':') goto notime;
q++;
while (*q >= '0' && *q <= '9') { s = s * 10 + (*q - '0'); sd++; q++; }
if (sd == 0) goto notime;
long frac = 0; int fd = 0; long scale = 10;
if (*q == '.') {
q++;
while (*q >= '0' && *q <= '9') { frac = frac * 10 + (*q - '0'); fd++; q++; }
if (fd == 1) scale = 100;
else if (fd == 2) scale = 10;
else if (fd >= 3) { scale = 1; while (fd > 3) { frac /= 10; fd--; } } /* >3 位截断到 ms */
else scale = 0; /* 孤立 '.' 无数字:忽略小数 */
}
if (*q != ']') goto notime;
*out_ms = (long)m * 60000L + (long)s * 1000L + frac * scale;
*next = q + 1;
return 1;
notime:
{
const char *r = strchr(p, ']');
*next = r ? r + 1 : NULL;
return 0;
}
}
static void lrc_parse(const char *lrc, int is_trans)
{
if (!lrc) return;
@@ -62,19 +97,16 @@ static void lrc_parse(const char *lrc, int is_trans)
int have = 0;
long first_ms = 0;
while (*p == '[') {
int m = 0, s = 0, cs = 0;
if (sscanf(p, "[%d:%d.%d]", &m, &s, &cs) >= 2) {
long ms = (long)m * 60000L + (long)s * 1000L + (long)cs * 10L;
long ms;
const char *after;
if (parse_lrc_tag(p, &ms, &after)) {
if (!have) { first_ms = ms; have = 1; }
/* 跳到本标签结尾 */
const char *q = strchr(p, ']');
if (!q) break;
p = q + 1;
p = after;
} else {
/* 非时间标签([ti:..] 等)——跳过到行尾 */
const char *q = strchr(p, ']');
if (!q) return;
p = q + 1;
if (!after) return; /* 无闭合 ']':残缺文件 */
p = after;
/* 若行内还有时间标签继续解析(极少),否则整行的正文以第一个文本为准 */
break;
}