feat(ncm §20): 音质体系全量——八档UI/VIP检测缓存/逐级降级下首恢复/fee门禁/默认极高HQ
- core: 9档枚举(顺序=降级链, DOLBY映射保留不进UI/链) + level_key(sky/sky_c51)
+ level_gate(CVIP/SVIP门槛) + song_url surround档附加immerseType=c51
+ song_url_auto全档表降级(out_level回填/响应level静默降档采信/try_fn可注入)
+ vip_info(weapi v2优先v1兜底) + parse_vip_info离线可测 + NCM_ERR_VIP(-15)
- app/logic/account.c(新): account.json会员态缓存(uid/nick/vip/双expire/checked_at)
刷新=登录后/超1天/到期后各一次不轮询; account_vip本地到期降档视图
- player_manager: 用户档按会员钳顶后逐级降级(不硬拒,缓存键仍用户档),
fee∈{1,4}且非会员→NCM_ERR_VIP零请求(缓存命中优先), eff_level回填
- UI: 设置页八档(规格+门槛+未解锁标注,允许选定), 播放页时间行中央
生效档/已降级至xx小字, VIP专享文案; 默认档Hi-Res→极高HQ(存量设置保留)
- 验证(0真实请求): degrade_sim 90例矩阵全PASS; player_flow_sim P1-P8全PASS;
test_ncm_core vip解析9例宿主+qemu全PASS; 窗口/分页/缓存/封面/audiod sim全PASS;
mipsel -Wall -Wextra 0 warning; 6页离屏rc=0。未部署真机(留主会话)
This commit is contained in:
1 parent
ab4dfb4f9c
commit
bdd9e898f4
23 files changed
+1026
-70
No files matched your search
@@ -267,6 +267,85 @@ static void test_track_all_parse(void) {
|
||||
CHECK(songs[0].duration_ms == 253000 && songs[0].fee == 1, "song0 dt/fee");
|
||||
}
|
||||
|
||||
static void test_vip_parse(void) {
|
||||
printf("== vip/info 离线解析 + 档位表(§20)==\n");
|
||||
int64_t now = 1700000000000LL; /* 固定参考时刻(ms) */
|
||||
ncm_vip_info_t v;
|
||||
|
||||
/* 1) v2 形态(data 子对象):associator.vipCode>0 + 未到期 → SVIP */
|
||||
const char *j1 =
|
||||
"{\"code\":200,\"data\":{\"associator\":{\"vipCode\":100,\"isSign\":true,"
|
||||
"\"expireTime\":1700000100000},\"musicPackage\":{\"vipCode\":0,\"expireTime\":0},"
|
||||
"\"redVipLevel\":0}}";
|
||||
CHECK(ncm_api_parse_vip_info(j1, strlen(j1), now, &v) == NCM_OK &&
|
||||
v.vip == NCM_VIP_SVIP && v.svip_expire_ms == 1700000100000LL,
|
||||
"v2 associator -> SVIP (vip=%d exp=%lld)", v.vip, (long long)v.svip_expire_ms);
|
||||
|
||||
/* 2) 音乐包(musicPackage.vipCode>0),无 associator → CVIP */
|
||||
const char *j2 =
|
||||
"{\"code\":200,\"data\":{\"musicPackage\":{\"vipCode\":220,\"isSign\":false,"
|
||||
"\"expireTime\":1700000200000},\"redVipLevel\":0}}";
|
||||
CHECK(ncm_api_parse_vip_info(j2, strlen(j2), now, &v) == NCM_OK &&
|
||||
v.vip == NCM_VIP_CVIP && v.cvip_expire_ms == 1700000200000LL,
|
||||
"v2 musicPackage -> CVIP (vip=%d)", v.vip);
|
||||
|
||||
/* 3) 全零 → NONE */
|
||||
const char *j3 =
|
||||
"{\"code\":200,\"data\":{\"associator\":{\"vipCode\":0,\"expireTime\":0},"
|
||||
"\"musicPackage\":{\"vipCode\":0},\"redVipLevel\":0,\"redplusLevel\":0}}";
|
||||
CHECK(ncm_api_parse_vip_info(j3, strlen(j3), now, &v) == NCM_OK && v.vip == NCM_VIP_NONE,
|
||||
"all-zero -> NONE (vip=%d)", v.vip);
|
||||
|
||||
/* 4) 到期时间已过且 vipCode=0 → NONE(不复活) */
|
||||
const char *j4 =
|
||||
"{\"code\":200,\"data\":{\"associator\":{\"vipCode\":0,\"isSign\":false,"
|
||||
"\"expireTime\":1699999900000}}}";
|
||||
CHECK(ncm_api_parse_vip_info(j4, strlen(j4), now, &v) == NCM_OK && v.vip == NCM_VIP_NONE,
|
||||
"expired associator -> NONE (vip=%d)", v.vip);
|
||||
|
||||
/* 5) 仅 expireTime > now(vipCode=0/isSign=false)→ 仍判 SVIP(BIZ_NOTES §1.2 字段判据) */
|
||||
const char *j5 =
|
||||
"{\"code\":200,\"data\":{\"associator\":{\"vipCode\":0,\"isSign\":false,"
|
||||
"\"expireTime\":1700000300000}}}";
|
||||
CHECK(ncm_api_parse_vip_info(j5, strlen(j5), now, &v) == NCM_OK && v.vip == NCM_VIP_SVIP,
|
||||
"expireTime>now alone -> SVIP (vip=%d)", v.vip);
|
||||
|
||||
/* 6) v1 兜底形态(字段直接在根部):vipStatus=true → CVIP */
|
||||
const char *j6 = "{\"code\":200,\"vipStatus\":true,\"redVipLevel\":0}";
|
||||
CHECK(ncm_api_parse_vip_info(j6, strlen(j6), now, &v) == NCM_OK && v.vip == NCM_VIP_CVIP,
|
||||
"v1 flat root vipStatus -> CVIP (vip=%d)", v.vip);
|
||||
|
||||
/* 7) 顶层红胶等级 redVipLevel>0 → CVIP */
|
||||
const char *j7 = "{\"code\":200,\"data\":{\"redVipLevel\":5}}";
|
||||
CHECK(ncm_api_parse_vip_info(j7, strlen(j7), now, &v) == NCM_OK && v.vip == NCM_VIP_CVIP,
|
||||
"redVipLevel>0 -> CVIP (vip=%d)", v.vip);
|
||||
|
||||
/* 8) code != 200 → 错误(不判档) */
|
||||
const char *j8 = "{\"code\":301,\"msg\":\"需要登录\"}";
|
||||
CHECK(ncm_api_parse_vip_info(j8, strlen(j8), now, &v) == NCM_ERR_AUTH,
|
||||
"code=301 -> AUTH err");
|
||||
|
||||
/* 9) 档位表:level 串 / 缓存键 / 门槛 */
|
||||
CHECK(strcmp(ncm_api_level_str(NCM_LEVEL_SURROUND), "sky") == 0 &&
|
||||
strcmp(ncm_api_level_str(NCM_LEVEL_SKY), "sky") == 0,
|
||||
"SURROUND/SKY api str both 'sky'");
|
||||
CHECK(strcmp(ncm_api_level_key(NCM_LEVEL_SURROUND), "sky_c51") == 0 &&
|
||||
strcmp(ncm_api_level_key(NCM_LEVEL_SKY), "sky") == 0,
|
||||
"cache keys unique: sky_c51 vs sky");
|
||||
CHECK(strcmp(ncm_api_level_str(NCM_LEVEL_JYEFFECT), "jyeffect") == 0 &&
|
||||
strcmp(ncm_api_level_str(NCM_LEVEL_JYMASTER), "jymaster") == 0 &&
|
||||
strcmp(ncm_api_level_str(NCM_LEVEL_DOLBY), "dolby") == 0,
|
||||
"jyeffect/jymaster/dolby str (BIZ_NOTES §2.1)");
|
||||
CHECK(ncm_api_level_gate(NCM_LEVEL_STANDARD) == NCM_VIP_NONE &&
|
||||
ncm_api_level_gate(NCM_LEVEL_EXHIGH) == NCM_VIP_NONE &&
|
||||
ncm_api_level_gate(NCM_LEVEL_LOSSLESS) == NCM_VIP_CVIP &&
|
||||
ncm_api_level_gate(NCM_LEVEL_HIRES) == NCM_VIP_CVIP &&
|
||||
ncm_api_level_gate(NCM_LEVEL_JYEFFECT) == NCM_VIP_SVIP &&
|
||||
ncm_api_level_gate(NCM_LEVEL_SURROUND) == NCM_VIP_SVIP &&
|
||||
ncm_api_level_gate(NCM_LEVEL_DOLBY) == NCM_VIP_SVIP,
|
||||
"level gates: free/CVIP/SVIP");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *data_dir = (argc > 1) ? argv[1] : "/tmp/ncm_test";
|
||||
printf("ncm_core self-test, data_dir=%s\n", data_dir);
|
||||
@@ -282,6 +361,7 @@ int main(int argc, char **argv) {
|
||||
test_qr();
|
||||
test_network();
|
||||
test_track_all_parse();
|
||||
test_vip_parse();
|
||||
|
||||
printf("\n%s\n", g_fail ? "== RESULT: FAIL ==" : "== RESULT: PASS ==");
|
||||
return g_fail ? 1 : 0;
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user