feat(ncm): 下载落盘名用响应实际档 + 命中按文件名标实际档(M6 播放链路)
- song_download_attempt:URL/类型确定后算实际档,.part/open/commit 统一用实际档键 (hires 请求被降级为 320k mp3 → song_<id>_exhigh.mp3),eff_level=实际档 - song_local_file:命中路径由 ncm_cache_level_from_path 取文件名档位并按容器钳顶 (mp3 永不高于极高),旧 hires.mp3 正确显示「极高」 - 移除旧「stale-degraded 改名+强制重下」与「无档位键迁移」:老卡文件读可用、不迁移 - player_flow_sim 适配:补实际档/路径档位桩与 M5 链接桩;P7/P12 期望键改实际档, P13 改为旧命名命中即播、不重下不迁移
This commit is contained in:
1 parent
30ffe469c8
commit
68833cf642
2 files changed
+152
-109
No files matched your search
@@ -14,8 +14,8 @@
|
||||
* P12 切音质后重播同一曲(§25):同 idx 在播时再 play —— 修前被解码主循环忽略
|
||||
* (旧曲继续出声、state 恒 BUFFER/「缓冲中」,收尾被外层当播完顺延下一首);
|
||||
* 修后 seq 变化即重启换档,状态回 PLAYING、新档位下载、eff_level 回填。
|
||||
* P13 陈旧 hires.mp3(flac 门禁前静默降级产物,hires 键下存 mp3):song_local_file
|
||||
* 迁移重命名到 exhigh 键(song_<id>_exhigh.mp3)+ 本档视为 miss 走下载(§NEW 任务2)。
|
||||
* P13 旧命名 hires.mp3(卡上存量:hires 键下存 mp3):M6 起按文件名档位+容器钳顶显示
|
||||
* EXHIGH 直接播放,不重下、不迁移(老卡文件继续可播/可删/可淘汰)。
|
||||
* P14 歌单里点「正在播的同曲」→ 只聚焦不重播(任务3 re-entry,同曲 id 判据)。
|
||||
* P15 跨歌单切换后同一队列下标点歌 → 不被同曲护栏吞,起播(Bug2 回归:真机日志
|
||||
* `play: re-enter current idx=0 state=2 -> no restart`,新歌永不播)。
|
||||
@@ -87,12 +87,22 @@ int appstate_queue_song(int idx, ncm_song_t *out) { return appstate_song_copy(id
|
||||
* sim 恒歌单窗口队列、搜索数组为空。appstate.c 未链入,两符号此处提供桩。 */
|
||||
int appstate_get_queue(void) { return NCM_QUEUE_PLAYLIST; }
|
||||
int appstate_search_song(int i, ncm_song_t *out) { (void)i; (void)out; return NCM_ERR_ARG; }
|
||||
/* M5 已缓存列表 / 文件内 tag:本 sim 不链 appstate.c、ncm_tag.c(缓存列表语义由 M5/M6 的
|
||||
* core 测试覆盖),仅提供链接桩避免 gc-sections 之外的空洞。 */
|
||||
int appstate_cache_list_load(void) { return 0; }
|
||||
int appstate_cache_count(void) { return 0; }
|
||||
int appstate_cache_item(int i, ncm_cache_item_t *out) { (void)i; (void)out; return NCM_ERR_ARG; }
|
||||
int appstate_cache_song(int i, ncm_song_t *out) { (void)i; (void)out; return NCM_ERR_ARG; }
|
||||
int appstate_cache_remove(int i) { (void)i; return NCM_ERR_ARG; }
|
||||
void appstate_cache_clear(void) { }
|
||||
int ncm_tag_embed_file(const char *path, const char *ext, const ncm_tag_meta_t *m)
|
||||
{ (void)path; (void)ext; (void)m; return NCM_ERR_ARG; } /* 降级为裸流(生产路径容忍) */
|
||||
|
||||
/* ---- 缓存/网络 stub ----
|
||||
* g_cache_ok=1 → lookup 命中(给真实存在的 /tmp 文件,免下载);
|
||||
* 否则走下载:song_url(g_url_rc 可注入失败) → http_download(g_dl_delay_ms 模拟慢) → commit。 */
|
||||
static int g_cache_ok = 1;
|
||||
static char g_cache_real[600]; /* g_cache_ok==2:lookup 命中返回此真实路径(P13 陈旧 mp3 迁移用) */
|
||||
static char g_cache_real[600]; /* g_cache_ok==2:lookup 命中返回此真实路径(P13 旧命名命中用) */
|
||||
static int g_url_rc = NCM_OK;
|
||||
static int g_dl_delay_ms = 0;
|
||||
static int g_downloads; /* http_download 调用数(song_url 请求放大检测) */
|
||||
@@ -138,7 +148,7 @@ static void pf_ensure_sim_mp3(void)
|
||||
int ncm_cache_lookup(const ncm_cache_t *c, const char *id, const char *ext, char *out, size_t out_sz)
|
||||
{
|
||||
(void)c; (void)id;
|
||||
if (g_cache_ok == 2) { /* P13 真实命中模式:仅 mp3 命中(stale hires 实体是 mp3) */
|
||||
if (g_cache_ok == 2) { /* P13 真实命中模式:仅 mp3 命中(旧命名 hires.mp3 实体是 mp3) */
|
||||
if (ext && strcmp(ext, "mp3") == 0 && g_cache_real[0]) {
|
||||
snprintf(out, out_sz, "%s", g_cache_real);
|
||||
return NCM_OK;
|
||||
@@ -195,6 +205,59 @@ int ncm_cache_path(const ncm_cache_t *c, const char *id, const char *ext, char *
|
||||
(c && c->dir[0]) ? c->dir : "/tmp", id ? id : "", e ? e : "bin");
|
||||
return NCM_OK;
|
||||
}
|
||||
/* M6:player_manager 用 core 的实际档判定/路径档位解析(本 sim 不链 core/ncm_cache.c,
|
||||
* 此处按 core 同规则提供桩——命名规则本身由 test/test_ncm_cache_host 全量断言)。 */
|
||||
int ncm_cache_actual_level(int req, const ncm_audio_info_t *ai, int resp_level)
|
||||
{
|
||||
int cap = -1, v;
|
||||
if (resp_level < 0) resp_level = req;
|
||||
if (ai && ai->container[0]) {
|
||||
if (strcmp(ai->container, "flac") == 0) {
|
||||
if (ai->bits_per_sample <= 0 || ai->sample_rate <= 0) cap = -1;
|
||||
else if (ai->bits_per_sample <= 16) cap = NCM_LEVEL_LOSSLESS;
|
||||
else if (ai->sample_rate > 96000) cap = NCM_LEVEL_JYMASTER;
|
||||
else if (ai->sample_rate > 48000) cap = NCM_LEVEL_JYEFFECT;
|
||||
else cap = NCM_LEVEL_HIRES;
|
||||
} else if (strcmp(ai->container, "mp3") == 0) {
|
||||
if (ai->bitrate_kbps > 128) cap = NCM_LEVEL_EXHIGH;
|
||||
else if (ai->bitrate_kbps > 0) cap = NCM_LEVEL_STANDARD;
|
||||
}
|
||||
}
|
||||
if (ai && strcmp(ai->container, "mp3") == 0) {
|
||||
if (cap < 0) cap = NCM_LEVEL_EXHIGH;
|
||||
if (resp_level >= 0 && resp_level < cap) cap = resp_level;
|
||||
} else if (cap < 0) {
|
||||
cap = resp_level;
|
||||
} else if (resp_level >= 0 && resp_level < cap) {
|
||||
cap = resp_level;
|
||||
}
|
||||
if (req < 0) v = (cap < 0) ? -1 : cap;
|
||||
else if (cap < 0) v = req;
|
||||
else v = (req < cap) ? req : cap;
|
||||
return v;
|
||||
}
|
||||
int ncm_cache_level_from_path(const char *path)
|
||||
{
|
||||
const char *base, *dot, *us = NULL, *us2 = NULL;
|
||||
char key[20];
|
||||
size_t n;
|
||||
int lv;
|
||||
if (!path || !path[0]) return -1;
|
||||
base = strrchr(path, '/'); base = base ? base + 1 : path;
|
||||
dot = strrchr(base, '.'); if (!dot) dot = base + strlen(base);
|
||||
for (const char *p = base; p < dot; p++) if (*p == '_') { us2 = us; us = p; }
|
||||
if (!us || us + 1 >= dot) return -1;
|
||||
n = (size_t)(dot - us - 1);
|
||||
if (n >= sizeof key) return -1;
|
||||
memcpy(key, us + 1, n); key[n] = 0;
|
||||
lv = ncm_api_level_from_key(key);
|
||||
if (lv >= 0 || !us2 || us2 + 1 >= us - 1) return lv;
|
||||
n = (size_t)(dot - us2 - 1);
|
||||
if (n >= sizeof key) return -1;
|
||||
memcpy(key, us2 + 1, n); key[n] = 0;
|
||||
return ncm_api_level_from_key(key);
|
||||
}
|
||||
|
||||
|
||||
int ncm_api_song_url_auto(const char *dev, const char *ck, const char *id, int level,
|
||||
ncm_play_url_t *out, int *out_level)
|
||||
@@ -310,6 +373,20 @@ const char *ncm_api_level_key(int level)
|
||||
default: return "standard";
|
||||
}
|
||||
}
|
||||
int ncm_api_level_from_key(const char *key)
|
||||
{
|
||||
if (!key || !key[0]) return -1;
|
||||
if (!strcmp(key, "sky_c51")) return NCM_LEVEL_SURROUND;
|
||||
if (!strcmp(key, "sky")) return NCM_LEVEL_SKY;
|
||||
if (!strcmp(key, "jymaster")) return NCM_LEVEL_JYMASTER;
|
||||
if (!strcmp(key, "jyeffect")) return NCM_LEVEL_JYEFFECT;
|
||||
if (!strcmp(key, "hires")) return NCM_LEVEL_HIRES;
|
||||
if (!strcmp(key, "lossless")) return NCM_LEVEL_LOSSLESS;
|
||||
if (!strcmp(key, "exhigh")) return NCM_LEVEL_EXHIGH;
|
||||
if (!strcmp(key, "higher")) return NCM_LEVEL_HIGHER;
|
||||
if (!strcmp(key, "standard")) return NCM_LEVEL_STANDARD;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---- 音频输出 stub:g_write_fail 注入写失败 ---- */
|
||||
static int g_write_fail;
|
||||
@@ -467,8 +544,8 @@ int main(void)
|
||||
g_url_req_level, NCM_LEVEL_EXHIGH);
|
||||
CHECK(player_manager_eff_level() == NCM_LEVEL_EXHIGH,
|
||||
"eff_level=%d (expect EXHIGH,UI 标「已降级至 极高 HQ」)", player_manager_eff_level());
|
||||
CHECK(strcmp(g_last_key, "1000_sky_c51") == 0,
|
||||
"cache key=%s (expect 1000_sky_c51 = id+用户档,降级产物归用户档键)", g_last_key);
|
||||
CHECK(strcmp(g_last_key, "1000_exhigh") == 0,
|
||||
"cache key=%s (expect 1000_exhigh = 实际档键,非用户档 sky_c51)", g_last_key);
|
||||
/* SVIP:同一用户档不钳顶,直达 SURROUND。
|
||||
* §21 时序:commit 移到 song0 曲尾 dispose(边下边播)——先 stop 阻断外层自动顺延
|
||||
* 到 song1 的竞态,再等 commit 落地后才切缓存开关,否则 commit stub 会把
|
||||
@@ -500,8 +577,7 @@ int main(void)
|
||||
player_manager_error());
|
||||
CHECK(g_url_calls == 0, "song_url 零请求(省一次请求,防风控;calls=%d)", g_url_calls);
|
||||
/* 已缓存的 VIP 曲:门禁不拦(缓存优先,NCM_CACHE_DESIGN §2.4)。
|
||||
* 注:把档位降回 EXHIGH——缓存 stub 的「命中」恒是 mp3;用户档若仍 SURROUND(>EXHIGH),
|
||||
* 会被 §NEW 任务2 当作静默降级产物迁移/重下,与「绕过门禁」的本意不符(P13 单独测迁移)。 */
|
||||
* 档位设 EXHIGH:stub 缓存恒返回 mp3(mp3 实际档不高于极高),与条目标注一致。 */
|
||||
player_manager_set_quality(NCM_LEVEL_EXHIGH, 10);
|
||||
g_cache_ok = 1;
|
||||
player_manager_play(2);
|
||||
@@ -595,8 +671,8 @@ int main(void)
|
||||
CHECK(wait_state(NCM_PC_PLAYING, 4000),
|
||||
"re-tap same idx -> back to PLAYING (expect 换档开播,非卡 缓冲中)");
|
||||
CHECK(g_downloads >= 1, "re-tap started new-quality download (downloads=%d)", g_downloads);
|
||||
CHECK(strcmp(g_last_key, "1000_lossless") == 0,
|
||||
"re-tap cache key=%s (expect 1000_lossless = 新档位)", g_last_key);
|
||||
CHECK(strcmp(g_last_key, "1000_exhigh") == 0,
|
||||
"re-tap cache key=%s (expect 1000_exhigh = 实际档,非会员钳顶后 320k mp3)", g_last_key);
|
||||
/* sim 账号非会员 → lossless 被 §20 钳顶到 EXHIGH;关键是 eff_level 已按新意图回填
|
||||
* (UI 据此显示实际生效档,不再停 BUFFER/「缓冲中」)。 */
|
||||
CHECK(player_manager_eff_level() == NCM_LEVEL_EXHIGH,
|
||||
@@ -604,30 +680,30 @@ int main(void)
|
||||
g_eof = 1;
|
||||
wait_state(NCM_PC_STOPPED, 3000);
|
||||
|
||||
printf("== P13: 陈旧 hires.mp3(静默降级产物)-> exhigh 迁移 + 本档 miss 重下(§NEW 任务2)==\n");
|
||||
printf("== P13: 旧命名 hires.mp3(实际 exhigh)-> 命中播放 + 实际档显示,不重下不迁移(M6)==\n");
|
||||
usleep(300 * 1000);
|
||||
g_eof = 0; g_reads_left = -1; g_url_rc = NCM_OK; g_fee = 0;
|
||||
g_part_size = 0; g_http_status = 200; g_416_first = 0; g_dl_delay_ms = 0; g_dl_fail = 0;
|
||||
g_vip = NCM_VIP_NONE; g_writes = 0; g_downloads = 0; g_url_calls = 0;
|
||||
g_last_key[0] = 0; g_last_ext[0] = 0; g_url_type = "mp3";
|
||||
/* 用户档 = Hi-Res(>EXHIGH),缓存实体是一首 mp3 = flac 门禁前把 exhigh 的 mp3 写进 hires
|
||||
* 键的「静默降级」产物。须被迁移到 exhigh 键(song_1000_exhigh.mp3),本档视为 miss 走下载。 */
|
||||
unlink("/tmp/song_1000_exhigh.mp3"); /* 保证 exhigh 目标不存在(走 rename 而非删旧) */
|
||||
/* 用户档 = Hi-Res(>EXHIGH),卡上存量是旧命名 song_1000_hires.mp3,实体是 320k mp3。
|
||||
* M6:文件名语义改为「实际档」后,此条目按文件名档位+容器钳顶显示 EXHIGH,直接播放;
|
||||
* 不迁移、不重下(旧文件继续可播/可删/可淘汰)。 */
|
||||
FILE *hf = fopen("/tmp/pf_hires.mp3", "wb");
|
||||
if (hf) { fputs("DEGRADED", hf); fclose(hf); }
|
||||
snprintf(g_cache_real, sizeof g_cache_real, "/tmp/pf_hires.mp3");
|
||||
g_cache_ok = 2; /* lookup 命中返回 g_cache_real(仅 mp3 键) */
|
||||
player_manager_set_quality(NCM_LEVEL_HIRES, 10);
|
||||
player_manager_play(0);
|
||||
CHECK(wait_state(NCM_PC_PLAYING, 3000), "stale hires.mp3 redownloaded -> plays");
|
||||
CHECK(g_downloads >= 1, "stale mp3 triggered redownload (downloads=%d)", g_downloads);
|
||||
CHECK(strcmp(g_last_key, "1000_hires") == 0,
|
||||
"redownload cache key=%s (expect 1000_hires=用户档键)", g_last_key);
|
||||
CHECK(strcmp(g_last_ext, "mp3") == 0, "redownload ext=%s (请求档为 mp3,维持原身份)", g_last_ext);
|
||||
CHECK(wait_state(NCM_PC_PLAYING, 3000), "legacy hires.mp3 hit -> plays directly");
|
||||
CHECK(g_downloads == 0, "legacy entry hit -> no redownload (downloads=%d)", g_downloads);
|
||||
CHECK(player_manager_eff_level() == NCM_LEVEL_EXHIGH,
|
||||
"eff_level=%d (expect EXHIGH:mp3 实体不可能高于极高)", player_manager_eff_level());
|
||||
struct stat mm;
|
||||
CHECK(stat("/tmp/song_1000_exhigh.mp3", &mm) == 0,
|
||||
"exhigh migrated file exists (选「极高」档仍可用)");
|
||||
CHECK(stat("/tmp/pf_hires.mp3", &mm) != 0, "stale hires.mp3 source gone (已迁走)");
|
||||
CHECK(stat("/tmp/pf_hires.mp3", &mm) == 0 && mm.st_size == 8,
|
||||
"legacy file untouched (no migration)");
|
||||
CHECK(stat("/tmp/song_1000_exhigh.mp3", &mm) != 0,
|
||||
"no renamed copy created (old card files need no migration)");
|
||||
g_cache_ok = 0; g_cache_real[0] = 0;
|
||||
g_eof = 1;
|
||||
wait_state(NCM_PC_STOPPED, 3000);
|
||||
|
||||
Reference in new issue
Block a user