fix(ncm): 三连修 — 封面切歌不更新/缓冲无反馈失败无声/暂停切歌边界 (REPORT §18)

- cover worker 忙等死循环(成功后不清目标,每圈解码+换入+gen++,CPU 100%+每帧整盘重绘)
  → 成功清目标转怠速; 失败指数退避 0.5-4s 连 8 次放弃+WARN 日志; 去重加在拉目标;
  cover_get 持锁+cover_put 修绘制期 UAF; g_host_mock 防真网守卫
- 封面兜底污染修复: 不再把单曲 al_pic 写进 g_st.cover_url(否则 al_pic 缺失曲回落成
  上一首封面且撞去重 → 封面永停); plr_page_enter 强制重估自愈; pl_page_enter 回填歌单封面
- 缓冲 UI: BUFFER 态「缓冲中… N%」(g_play.dl_done/total, 下载回调累加); 播放错误中文
  错误条+snack(旧版 err 无 UI 消费者=无声死等); STOPPED 态播放键=重试; play/next 清 err
- 写失败 set_err 修连环跳歌(旧版静默 break 被外层当自然播完,流死时跳完歌单+狂发 song_url);
  still_current 护栏修下载途中切歌的状态/快照过期覆盖
- 验证(0 真实请求): cover_chain_sim 7 组+player_flow_sim P1-P5 全 PASS, 旧代码对照
  T2/P2/P4 必败坐实根因; audiod_chain_sim 14/14; pagination/window/core+qemu 回归 PASS;
  宿主+mipsel 自有代码 0 warning; 9 页离屏渲染 rc=0 (含新 mock 页 playerbuf/playererr)
- ncm md5 50d41bbf4a379e925699ba013b997ffd (未部署真机, 留主会话)
This commit is contained in:
MeowOS committed 2026-09-01 05:29:09 +08:00
1 parent 614694ad4a
commit 6200a41b71
13 files changed
+820 -20

No files matched your search

+41 -1
View File
@@ -41,6 +41,15 @@ int64_t player_manager_pos_ms(void) { int64_t p; pthread_mutex_lock(&g_play.mu);
int64_t player_manager_dur_ms(void) { int64_t d; pthread_mutex_lock(&g_play.mu); d = g_play.dur_ms; pthread_mutex_unlock(&g_play.mu); return d; }
int player_manager_error(void) { int e; pthread_mutex_lock(&g_play.mu); e = g_play.err; pthread_mutex_unlock(&g_play.mu); return e; }
/* 本曲下载进度(UI 缓冲反馈,§18)。total=0 表示未知(song_url 未给 size / 缓存命中)。 */
void player_manager_dl(int64_t *done, int64_t *total)
{
pthread_mutex_lock(&g_play.mu);
if (done) *done = g_play.dl_done;
if (total) *total = g_play.dl_total;
pthread_mutex_unlock(&g_play.mu);
}
/* 播放曲快照(REPORT §17):run_song 起播时物化并拷入 g_play.play_song(mu 保护),
* 之后播放/歌词/IPC/UI 一律读快照——窗口滑出当前播放曲不影响播放,不许崩。 */
int player_manager_current_song(ncm_song_t *out)
@@ -70,6 +79,9 @@ void player_manager_play(int song_idx)
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
g_play.pos_ms = 0;
g_play.err = 0; /* 新播放意图清旧错误(UI 错误条随新点歌消失) */
g_play.dl_done = 0;
g_play.dl_total = 0;
g_play.seq++;
pthread_mutex_unlock(&g_play.mu);
}
@@ -101,6 +113,9 @@ void player_manager_next(int dir)
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
g_play.pos_ms = 0;
g_play.err = 0;
g_play.dl_done = 0;
g_play.dl_total = 0;
g_play.seq++;
}
pthread_mutex_unlock(&g_play.mu);
@@ -120,6 +135,10 @@ void player_manager_stop(void)
/* ---- 每首歌的播放体(在外层循环内调用)---- */
static int download_cb(void *ctx, const unsigned char *data, size_t len)
{
/* 下载进度反馈(UI「缓冲中… N%」,§18):回调在播放线程,锁内仅累加。 */
pthread_mutex_lock(&g_play.mu);
g_play.dl_done += (int64_t)len;
pthread_mutex_unlock(&g_play.mu);
return ncm_cache_write((ncm_cache_writer_t *)ctx, data, len);
}
@@ -138,6 +157,9 @@ static int song_local_file(const char *id, char *path, size_t psz, int64_t *byte
int rc = ncm_api_song_url_auto(g_st.ctx.device_id, g_st.ctx.cookie, id, g_level, &u);
if (rc != NCM_OK) return rc;
if (!u.url[0]) return NCM_ERR_NOTFOUND;
pthread_mutex_lock(&g_play.mu);
g_play.dl_total = (u.size > 0) ? u.size : 0; /* 缓冲进度% 分母(0=未知只转菊花) */
pthread_mutex_unlock(&g_play.mu);
/* 写前检查:池大小超限先按 LRU 淘汰(此线程尚未开 temp,无在途文件被误删风险) */
ncm_cache_evict(&g_st.ctx.cache);
@@ -183,6 +205,16 @@ static void set_err(int err, const char *msg)
pthread_mutex_unlock(&g_play.mu);
}
/* 切歌竞态护栏(§18):物化/下载期间用户可能已切歌——只有仍是当前目标且处于 BUFFER
* (本次播放意图未过期)才继续,否则直接回外层(外层会以新 cur 重跑 run_song)。 */
static int still_current(int idx)
{
pthread_mutex_lock(&g_play.mu);
int ok = (g_play.cur_song == idx) && (g_play.state == NCM_PC_BUFFER);
pthread_mutex_unlock(&g_play.mu);
return ok;
}
/* 一首歌:下载→解码→输出,直到结束或被打断。返回后由外层处理下一首。 */
static void run_song(int idx)
{
@@ -193,6 +225,7 @@ static void run_song(int idx)
int fr = appstate_song_fetch(idx, &s);
if (fr != NCM_OK) { set_err(fr, "load failed"); return; }
}
if (!still_current(idx)) return; /* 物化期间已切歌/停止:不写过期快照 */
pthread_mutex_lock(&g_play.mu);
g_play.play_song = s;
g_play.play_song_valid = 1;
@@ -202,11 +235,13 @@ static void run_song(int idx)
char path[512];
int rc = song_local_file(s.id, path, sizeof path, &file_bytes);
if (rc != NCM_OK) {
if (!still_current(idx)) return; /* 下载期间已切歌:静默放弃(缓存已落盘不亏) */
const char *m = (rc == NCM_ERR_NOTFOUND) ? "no stream (VIP/copyright)"
: (rc == NCM_ERR_AUTH) ? "session expired" : "load failed";
set_err(rc, m);
return;
}
if (!still_current(idx)) return; /* 下载完成但已切歌:勿开解码/勿动状态 */
ncm_strlcpy(g_path, path, sizeof g_path);
g_file_bytes = file_bytes;
@@ -287,7 +322,12 @@ static void run_song(int idx)
break;
}
int frames = (int)(got / 2);
if (audio_out_write(pcm, frames) != frames) break;
/* 写失败(流死/audiod 断链):置错停播——旧版静默 break 会被外层当「自然播完」
* 自动顺延下一首,流死时连环跳歌并狂发 song_url 请求(§18 修)。 */
if (audio_out_write(pcm, frames) != frames) {
if (!g_app_quit) set_err(NCM_ERR_IO, "audio write failed");
break;
}
frames_done += frames;
/* 更新进度(~200ms 节流)*/
int64_t pos = frames_done * 1000 / 44100;