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:
1 parent
c2ee7626a4
commit
3d70a3dd2c
176 files changed
+15507
-597
No files matched your search
@@ -196,9 +196,54 @@ static int obj_bool(const ncm_json_t *j, int tok, const char *key) {
|
||||
return (strcmp(s, "true") == 0);
|
||||
}
|
||||
|
||||
/* UTF-8 整序列安全追加:把 src 按「多字节字符整序列」边界拷入 dst[*pos..],放不下整字符则
|
||||
* 停止(不切半个字)。sz 为 dst 总大小(含 NUL 位)。用于 art_name 多歌手拼接的字节截断。 */
|
||||
static void append_artist_name(char *dst, size_t *pos, size_t sz, const char *src)
|
||||
{
|
||||
size_t cap = sz - 1; /* 最多写 sz-1 字节,留 NUL */
|
||||
size_t i = 0;
|
||||
while (src[i] && *pos < cap) {
|
||||
unsigned char c = (unsigned char)src[i];
|
||||
size_t k = 1;
|
||||
if (c < 0x80) k = 1;
|
||||
else if ((c & 0xE0) == 0xC0) k = 2;
|
||||
else if ((c & 0xF0) == 0xE0) k = 3;
|
||||
else if ((c & 0xF8) == 0xF0) k = 4;
|
||||
else { dst[(*pos)++] = src[i++]; continue; } /* 非法起始字节:单字节原样收 */
|
||||
if (*pos + k > cap) break; /* 整字符放不下:丢弃(保住 UTF-8 完整性) */
|
||||
memcpy(dst + *pos, src + i, k);
|
||||
*pos += k; i += k;
|
||||
}
|
||||
dst[*pos] = 0;
|
||||
}
|
||||
|
||||
/* 歌曲 ar[](歌手数组)多歌手名用 '/' 拼接进 art_name(≤ out_sz,UTF-8 整序列截断)。
|
||||
* 单歌手行为不变(无 '/');空歌手条目跳过(避免连写分隔符「//」)。 */
|
||||
static void fill_artist(const ncm_json_t *j, int e, char *out, size_t out_sz)
|
||||
{
|
||||
out[0] = 0;
|
||||
int ar = ncm_json_obj_get(j, e, "ar");
|
||||
if (ar < 0 || ncm_json_type(j, ar) != JSMN_ARRAY) return;
|
||||
int nart = ncm_json_arr_len(j, ar);
|
||||
size_t pos = 0;
|
||||
for (int k = 0; k < nart; k++) {
|
||||
int a = ncm_json_arr_get(j, ar, k);
|
||||
if (a < 0) break;
|
||||
char nm[128]; /* 单歌手名缓冲(obj_str 已截断到 127 字节) */
|
||||
obj_str(j, a, "name", nm, sizeof nm);
|
||||
if (!nm[0]) continue; /* 空条目:跳过,不加分隔符 */
|
||||
if (pos > 0) { /* 第 2+ 歌手:先补 '/'(需 1 字节 + NUL) */
|
||||
if (pos >= out_sz - 1) break; /* 无空间放分隔符 */
|
||||
out[pos++] = '/';
|
||||
out[pos] = 0;
|
||||
}
|
||||
append_artist_name(out, &pos, out_sz, nm);
|
||||
}
|
||||
}
|
||||
|
||||
/* 解析一处「歌曲对象数组」(song_detail / cloudsearch / track_all 离线解析 共用)。
|
||||
* 元素字段:id/name/ar[0].name/al.name/al.picUrl/dt/fee。id 可为数字或字符串(均以文本拷贝,
|
||||
* 兼容 >2^31 的歌曲 id)。返回 NCM_OK,*out_n=写入数。 */
|
||||
* 元素字段:id/name/ar[].name(多歌手 '/' 拼接) /al.name/al.picUrl/dt/fee。id 可为数字或
|
||||
* 字符串(均以文本拷贝,兼容 >2^31 的歌曲 id)。返回 NCM_OK,*out_n=写入数。 */
|
||||
static int parse_songs_array(const ncm_json_t *j, int arr, ncm_song_t *out, int max, int *out_n)
|
||||
{
|
||||
*out_n = 0;
|
||||
@@ -213,11 +258,7 @@ static int parse_songs_array(const ncm_json_t *j, int arr, ncm_song_t *out, int
|
||||
obj_str(j, e, "name", out[i].name, sizeof out[i].name);
|
||||
out[i].duration_ms = obj_int(j, e, "dt");
|
||||
out[i].fee = (int)obj_int(j, e, "fee");
|
||||
int ar = ncm_json_obj_get(j, e, "ar");
|
||||
if (ar >= 0 && ncm_json_type(j, ar) == JSMN_ARRAY) {
|
||||
int a0 = ncm_json_arr_get(j, ar, 0);
|
||||
if (a0 >= 0) obj_str(j, a0, "name", out[i].art_name, sizeof out[i].art_name);
|
||||
}
|
||||
fill_artist(j, e, out[i].art_name, sizeof out[i].art_name);
|
||||
int al = ncm_json_obj_get(j, e, "al");
|
||||
if (al >= 0) {
|
||||
obj_str(j, al, "name", out[i].album_name, sizeof out[i].album_name);
|
||||
@@ -371,13 +412,15 @@ static int level_from_str(const char *s) {
|
||||
}
|
||||
|
||||
/* 账户权限允许的最高档(§20.3,与 app/logic/account.c account_level_cap 同口径):
|
||||
* NONE→EXHIGH / CVIP→HIRES / SVIP→SURROUND。core 侧权威实现,供降级链自钳顶。 */
|
||||
* NONE→EXHIGH / CVIP→HIRES / SVIP|SUPER→SURROUND。core 侧权威实现,供降级链自钳顶。
|
||||
* SUPER(超级会员)为最高档,与 SVIP 同享全景声顶档。 */
|
||||
static int level_cap_for_vip(int vip)
|
||||
{
|
||||
switch (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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,13 +40,19 @@ enum {
|
||||
const char *ncm_api_level_str(int level); /* API level 串(SKY/SURROUND 同为 "sky") */
|
||||
const char *ncm_api_level_key(int level); /* 缓存键唯一后缀(SKY="sky"/SURROUND="sky_c51") */
|
||||
|
||||
/* 会员等级(vip/info 判定,BIZ_NOTES §1):SVIP(黑胶)权限 ⊇ CVIP(畅听/音乐包)。 */
|
||||
/* 会员等级(vip/info 判定,BIZ_NOTES §1):用户语义 0=非会员 / 1=畅听会员(VIP) /
|
||||
* 2=黑胶会员(CVIP) / 3=超级会员(SVIP)。枚举名保留旧名(CVIP/SVIP)避免大改,新增
|
||||
* NCM_VIP_SUPER=3 为最高档;权限 SUPER ⊇ SVIP(黑胶) ⊇ CVIP(畅听/音乐包)。
|
||||
* 注:ncm_api_parse_vip_info 仍只产出 NONE/CVIP/SVIP(关联音乐包=CVIP、黑胶=SVIP,
|
||||
* 无独立“超级”字段);3 档目前经 account.json 持久化进入/显示(account_load 钳位已放行)。 */
|
||||
enum {
|
||||
NCM_VIP_NONE = 0,
|
||||
NCM_VIP_CVIP = 1,
|
||||
NCM_VIP_SVIP = 2,
|
||||
NCM_VIP_NONE = 0,
|
||||
NCM_VIP_CVIP = 1, /* 畅听会员/音乐包 */
|
||||
NCM_VIP_SVIP = 2, /* 黑胶会员 */
|
||||
NCM_VIP_SUPER = 3, /* 超级会员(最高档) */
|
||||
};
|
||||
/* 档位所需会员门槛:standard/higher/exhigh=NONE;lossless/hires=CVIP;其余=SVIP。 */
|
||||
/* 档位所需会员门槛:standard/higher/exhigh=NONE;lossless/hires=CVIP;其余=SVIP。
|
||||
* 门槛最高只到 SVIP(2)(超级会员=3 为 ⊇ 黑胶,天然通过所有门槛,无需单独门禁)。 */
|
||||
int ncm_api_level_gate(int level);
|
||||
|
||||
typedef struct ncm_playlist {
|
||||
@@ -65,7 +71,7 @@ typedef struct ncm_playlist {
|
||||
typedef struct ncm_song {
|
||||
char id[24];
|
||||
char name[128];
|
||||
char art_name[96]; /* 首个歌手名 */
|
||||
char art_name[96]; /* 歌手名:多歌手用 '/' 拼接(UTF-8 整序列截断) */
|
||||
char album_name[96];
|
||||
char al_pic[256]; /* 专辑封面 picUrl(song_detail/cloudsearch 解析;播放页黑胶封面) */
|
||||
int32_t duration_ms;
|
||||
|
||||
@@ -232,6 +232,11 @@ const char *ncm_cache_writer_tmp_path(const ncm_cache_writer_t *w) {
|
||||
return w ? w->tmp_path : "";
|
||||
}
|
||||
|
||||
/* 写者 final(正式)路径(commit 后 rename 目标;曲终 drop_finished L0 DONTNEED 用)。 */
|
||||
const char *ncm_cache_writer_final_path(const ncm_cache_writer_t *w) {
|
||||
return w ? w->final_path : "";
|
||||
}
|
||||
|
||||
/* 半成品判定(阶段3,§5.1):.tmp(下载中)/ .part(中断残体)不属正式缓存条目,
|
||||
* 不进 LRU 扫描/计数——否则正被下载播放的文件会被缓存上限误删(写者管理域)。 */
|
||||
static int is_partial_name(const char *name)
|
||||
|
||||
@@ -61,6 +61,8 @@ int ncm_cache_commit(ncm_cache_writer_t *w); /* win; 返回 0=成功 */
|
||||
int ncm_cache_abort(ncm_cache_writer_t *w);
|
||||
/* 写者 .tmp 路径(只读):边下边播解码侧用它开只读 fd 建 stream_src。 */
|
||||
const char *ncm_cache_writer_tmp_path(const ncm_cache_writer_t *w);
|
||||
/* 写者 final(正式)路径(只读):commit 后目标文件路径,用于曲终 drop_finished(L0 DONTNEED)。 */
|
||||
const char *ncm_cache_writer_final_path(const ncm_cache_writer_t *w);
|
||||
|
||||
/* ---- 阶段3:.part 断点续传 + 在播文件防淘汰(NCM_STREAM_PLAY §5) ----
|
||||
* 命名约定:中断的 .tmp 保留为 final+".part"(残体),供下次播放「Range 续传」;
|
||||
|
||||
@@ -491,6 +491,13 @@ static int http_run(const ncm_http_req_t *req, sink_t *sink, ncm_http_resp_t *re
|
||||
}
|
||||
}
|
||||
if (sink->overflow) { ret = NCM_ERR_FULL; goto done; }
|
||||
/* 截断守卫:body 未到达合法终态却已跳出读取循环(对端提前关闭 / PEER_CLOSE_NOTIFY)。
|
||||
* 合法终态只有两种:BS_DONE(定长收满 / chunked 到 0 分块 / Content-Length:0,body_st_init 即置)
|
||||
* 与 BS_CLOSE(无 Content-Length、依 RFC 靠对端关闭定界——关闭即合法结束)。
|
||||
* body_st_init 里 BS_CLOSE 仅在 content_length==SIZE_MAX 且非 chunked 时进入;若 Content-Length
|
||||
* 存在而未收满(此时必为 BS_LEN/BS_CHUNK_*),或 chunked 未到 0 分块就关闭,即为截断:
|
||||
* 此时返回错误,调用方(song_src_dispose)走 abort_part 保留 .part,不得 commit 残缺文件。 */
|
||||
if (bst.st != BS_DONE && bst.st != BS_CLOSE) { ret = NCM_ERR_IO; goto done; }
|
||||
ret = NCM_OK;
|
||||
done:
|
||||
mbedtls_net_free(&net);
|
||||
|
||||
Reference in new issue
Block a user