feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)
脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。
- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
(P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步
范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
This commit is contained in:
1 parent
e87ccdf061
commit
7c7d891570
59 files changed
+5305
-590
No files matched your search
@@ -537,7 +537,10 @@ int audio_out_pause(int paused); /* 1=暂停(停 PCM 时钟不丢流) 0=恢
|
||||
void audio_out_close(void);
|
||||
int audio_out_sync_volume(void); /* 任务1:读 settings.conf 当前系统音量并下发到 audiod
|
||||
(MSG_AUDIO_VOLUME→写 DAC+持久化)。0=下发成功/-1=失败。
|
||||
audio_out_open SINK_AUDIOD 成功后已自动调;独立导出供宿主验证与复调。 */
|
||||
每音频会话(新 audiod 流 sid)首同步一次 + 值变才重发——切歌/新建
|
||||
会话时 DAC 音量不残留默认响度(修「切歌音量骤增」);同会话内不
|
||||
重复建连(不做 fd 风暴)。audio_out_open SINK_AUDIOD 成功后已自动调;
|
||||
独立导出供宿主验证与复调。 */
|
||||
void audio_out_set_target(int ms); /* 预留:缓冲目标 */
|
||||
int audio_out_buffered_ms(void);
|
||||
|
||||
|
||||
@@ -233,20 +233,27 @@ static void audiod_sync_volume(int pct)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* 每进程同步一次 + 值变才重发:杜绝每次 audio_out_open 新建短连接(fd=N accept 风暴根因)。
|
||||
* g_vol_synced=1 即已同步过;g_vol_last=上次下发的 pct,二者相同则零连接直接返回。 */
|
||||
static int g_vol_synced = 0;
|
||||
static int g_vol_last = -1;
|
||||
/* 会话身份 = audiod 流 sid(g_sid):切歌/新建音频会话 = 每次 audio_out_open 都向 audiod 开一条
|
||||
* 新流、audiod 分配新 sid(单调不重用)。此前用进程级 g_vol_synced「首次同步一次」——切歌(新会话)
|
||||
* 时音量不再被重发到 audiod,DAC 落在会话建立/重配后的默认/上次残留值(衰减量 0=最响),造成
|
||||
* 「切歌音量骤增,按一下音量键才回落到设定值」(用户真机 2026-09-05)。audiod 只在 reconfigure
|
||||
* 且参数变化时才 re-apply 音量(_audio_mark_reconfig_needed 同参数跳过),本会话重发是权威兜底。
|
||||
* 修法:**每会话首同步一次**——新 sid(新会话)必重发一次当前音量;同会话内仅值变才重发。
|
||||
* 每会话恰 1 条短连接,不回到「每次 open 都建连」的 fd 风暴(§2026-09-02 根因,vol_sync_sim 60 次
|
||||
* 同值→1 连接)。音量值取自 settings.conf(audiod 权威同源同键),反转/真静音(255) 由 audiod
|
||||
* dac_vol_set 负责,本层只传 pct(0-100)。 */
|
||||
static int g_vol_synced_sid = -1; /* 上次音量同步针对的 audiod 流 sid;-1=从未(必发) */
|
||||
static int g_vol_last = -1;
|
||||
|
||||
int audio_out_sync_volume(void)
|
||||
{
|
||||
int pct = settings_read_volume();
|
||||
if (g_vol_synced && pct == g_vol_last) return 0; /* 已同步且未变:零新建连接 */
|
||||
g_vol_synced = 1;
|
||||
g_vol_last = pct;
|
||||
if (g_vol_synced_sid == g_sid && pct == g_vol_last) return 0; /* 本会话已同步且值未变:零新建连接 */
|
||||
g_vol_synced_sid = g_sid;
|
||||
g_vol_last = pct;
|
||||
audiod_sync_volume(pct);
|
||||
meow_log(MEOW_LOG_INFO, "audio: sync volume -> audiod pct=%d (settings %s)",
|
||||
pct, settings_conf_path());
|
||||
meow_log(MEOW_LOG_INFO, "audio: sync volume -> audiod pct=%d (settings %s, sid=%d)",
|
||||
pct, settings_conf_path(), g_sid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -379,6 +386,7 @@ int audio_out_pause(int paused)
|
||||
}
|
||||
g_sid = sid;
|
||||
meow_log(MEOW_LOG_INFO, "audio: audiod reopen ok sid=%d", sid);
|
||||
audio_out_sync_volume(); /* 新会话(新 sid):音量权威兜底重发一次(DAC 复位后不残留默认响度) */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1032,10 +1032,18 @@ static int g_lyr_thr_on;
|
||||
static volatile int g_lyr_state; /* 0=空闲/未拉 1=加载中 2=已拉完 */
|
||||
static char g_lyr_song_id[24]; /* 歌词归属歌曲 id */
|
||||
|
||||
/* 歌词拉取失败后的重试退避(ms,指数翻倍封顶 OS):掉线瞬间失败的请求**不**写死「无歌词」,
|
||||
* 交回退避重试;网络恢复后下一轮到点即重拉成功(真机症状:掉线一会儿再重连后恒「暂无歌词」)。 */
|
||||
#define LYRIC_RETRY_BASE_MS 1000
|
||||
#define LYRIC_RETRY_MAX_MS (30 * 1000)
|
||||
|
||||
static void *lyric_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
char last_id[24] = "";
|
||||
int last_ok = 1; /* last_id 对应结果是否成功(成功不重拉;失败记退避待重试) */
|
||||
int retry_delay = 0; /* 失败后逐次翻倍的退避基值(ms) */
|
||||
int64_t retry_at = 0; /* 单调时刻:下次重试;0=无待重试 */
|
||||
while (!g_app_quit) {
|
||||
usleep(300 * 1000);
|
||||
char id[24];
|
||||
@@ -1051,14 +1059,28 @@ static void *lyric_worker(void *arg)
|
||||
g_lyr_state = 0;
|
||||
pthread_mutex_unlock(&g_lyrmu);
|
||||
last_id[0] = 0;
|
||||
last_ok = 1;
|
||||
retry_delay = 0;
|
||||
retry_at = 0;
|
||||
continue;
|
||||
}
|
||||
if (!id[0]) continue;
|
||||
if (strcmp(id, last_id) == 0) continue; /* 同一曲已拉/在拉 */
|
||||
if (meow_cachegov_prefetch_paused()) continue; /* page-cache 治理(§2.3/§3):暂停歌词预取
|
||||
* (A 回退硬暂停 OR svc_perf C Cached 降载),
|
||||
* 不置 last_id → 降载恢复后下轮再拉。 */
|
||||
ncm_strlcpy(last_id, id, sizeof last_id);
|
||||
|
||||
int is_new = (strcmp(id, last_id) != 0);
|
||||
if (is_new) {
|
||||
if (meow_cachegov_prefetch_paused()) continue; /* page-cache 治理(§2.3/§3):暂停歌词预取
|
||||
* (A 回退硬暂停 OR svc_perf C Cached 降载),
|
||||
* 不置 last_id → 降载恢复后下轮再拉。 */
|
||||
ncm_strlcpy(last_id, id, sizeof last_id);
|
||||
last_ok = 0;
|
||||
retry_delay = 0;
|
||||
retry_at = 0;
|
||||
} else {
|
||||
if (last_ok) continue; /* 同一曲已成功拉完(含 NCM_OK 空词=真无词),不重拉 */
|
||||
if (meow_cachegov_prefetch_paused()) continue;
|
||||
if (retry_at > ncm_monotonic_ms()) continue; /* 失败退避未到 */
|
||||
/* 到点重试:保留 retry_delay 以便失败时继续翻倍 */
|
||||
}
|
||||
lyric_clear();
|
||||
pthread_mutex_lock(&g_lyrmu);
|
||||
g_lyr_state = 1; /* 加载中 */
|
||||
@@ -1068,22 +1090,32 @@ static void *lyric_worker(void *arg)
|
||||
char *tl = (char *)malloc(32 * 1024);
|
||||
if (!lrc || !tl) {
|
||||
free(lrc); free(tl);
|
||||
retry_delay = retry_delay ? (retry_delay * 2) : LYRIC_RETRY_BASE_MS;
|
||||
if (retry_delay > LYRIC_RETRY_MAX_MS) retry_delay = LYRIC_RETRY_MAX_MS;
|
||||
retry_at = ncm_monotonic_ms() + retry_delay;
|
||||
pthread_mutex_lock(&g_lyrmu); g_lyr_state = 0; pthread_mutex_unlock(&g_lyrmu);
|
||||
continue;
|
||||
}
|
||||
lrc[0] = tl[0] = 0; /* 默认空串(fetch 失败/空词时上报清词,防会话留旧曲歌词) */
|
||||
int rc = ncm_api_lyric(g_st.ctx.device_id, g_st.ctx.cookie, id, lrc, 64 * 1024, tl, 32 * 1024);
|
||||
if (rc == NCM_OK) {
|
||||
last_ok = 1; /* 真正结果:空词(NCM_OK 无词也算「已拉完」) */
|
||||
retry_delay = 0;
|
||||
retry_at = 0;
|
||||
lyric_parse(lrc, tl);
|
||||
ipc_media_lyric_report(lrc, tl); /* 批3:上报 live 歌词到媒体会话 */
|
||||
} else {
|
||||
/* 拉取失败(网络/HTTP/会话):不写死「无词」,交回退避重试;UI 呈「加载中」而非「暂无歌词」 */
|
||||
retry_delay = retry_delay ? (retry_delay * 2) : LYRIC_RETRY_BASE_MS;
|
||||
if (retry_delay > LYRIC_RETRY_MAX_MS) retry_delay = LYRIC_RETRY_MAX_MS;
|
||||
retry_at = ncm_monotonic_ms() + retry_delay;
|
||||
ipc_media_lyric_report("", ""); /* 无词/拉取失败:清 media session 该曲歌词 */
|
||||
}
|
||||
free(lrc); free(tl);
|
||||
|
||||
pthread_mutex_lock(&g_lyrmu);
|
||||
ncm_strlcpy(g_lyr_song_id, id, sizeof g_lyr_song_id);
|
||||
g_lyr_state = 2; /* 拉完(可能 0 行=无词) */
|
||||
g_lyr_state = last_ok ? 2 : 0; /* 2=已拉完(可 0 行=无词);0=失败待重试(UI 呈加载中) */
|
||||
pthread_mutex_unlock(&g_lyrmu);
|
||||
}
|
||||
return NULL;
|
||||
|
||||
@@ -26,8 +26,12 @@ static volatile int g_load_err;
|
||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||
|
||||
/* 歌单详情加载后台 worker(大歌单可达数十秒,不能占 UI 线程)。
|
||||
* §17 滑动窗口:worker 两种任务——①首屏(g_ens_first=1):重建 trackIds 索引 + 物化首段;
|
||||
* ②滑窗补载:物化视口区间 [g_ens_lo,g_ens_hi)(ensure_range,盘缓存优先)。 */
|
||||
* §17 滑动窗口:worker 三种任务——①首屏(g_ens_first=1):重建 trackIds 索引 + 物化第 0 页
|
||||
* (≤NCM_SONG_BATCH 首,一次一页——不再首屏连拉多批补满窗口,大歌单阻塞久且触发风控);
|
||||
* ②顺序下一页(g_ens_page=1):仅当用户滑到已分页底部(余量 ≤NCM_PL_PAGE_EDGE 首)时拉一页,
|
||||
* 成功推进 g_paged_hi,拉完该歌单全部曲目即止;③回滚补物化:已分页区间内被窗口挤出的
|
||||
* 失效槽按视口 ±24 补(盘缓存命中为主,不占网络分页额度)。 */
|
||||
#define NCM_PL_PAGE_EDGE 20 /* 懒加载触发余量(首):视口底距已分页底部 ≤20 首才拉下一页 */
|
||||
static pthread_mutex_t g_plmu = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t g_plthr;
|
||||
static int g_plthr_on;
|
||||
@@ -37,9 +41,11 @@ static volatile int g_plloading;
|
||||
static volatile int g_plloaded_idx;
|
||||
static volatile int g_plloaded_gen; /* worker 启动时代次(§26:过期结果丢弃判据) */
|
||||
static volatile int g_pl_stale; /* 1=本次结果被判过期(未写全局,需重拉当前目标) */
|
||||
static volatile int g_ens_lo, g_ens_hi; /* 滑窗补载目标区间(全局下标) */
|
||||
static volatile int g_ens_lo, g_ens_hi; /* 补载/下一页目标区间(全局下标) */
|
||||
static volatile int g_ens_first; /* 1=首屏重头(offset=0) */
|
||||
static volatile int g_ens_page; /* 1=顺序下一页任务(成功推进 g_paged_hi) */
|
||||
static volatile int g_ens_err; /* 最近一次滑窗补载错误(0=ok;首屏错误走 g_load_err) */
|
||||
static volatile int g_paged_hi; /* 已分页高水位:从顶部连续物化到的全局下标(下一页起点) */
|
||||
|
||||
/* 打开快照(§26 开A→未载完→开B 防串):refresh 在启动 worker 前把「目标歌单」拷进 g_tgt,
|
||||
* worker 启动时再拷到本地——detail 加载不再依赖 g_st.playlists[](会被回家时 home_worker 的
|
||||
@@ -239,12 +245,15 @@ static void pl_draw_footer(void *ctx)
|
||||
ncm_draw_footer(r_cont, foot);
|
||||
}
|
||||
|
||||
/* apply:reset_scroll=1 时列表回顶并清选中(首屏/刷新);0=保持当前滚动(滑窗补载,防跳顶)。 */
|
||||
/* apply:reset_scroll=1 时列表回顶并清选中(首屏/刷新);0=保持当前滚动(滑窗补载,防跳顶)。
|
||||
* 拉到数据后必须让列表项重新绑定/重绘:src2 每帧 live 取行(占位行 ↔ 已物化歌名/歌手),
|
||||
* 失效列表带 + 整帧重画把刚物化的歌名/歌手填进各项(修「拉完曲目行仍 ... 加载中」)。 */
|
||||
static void pl_apply(int reset_scroll)
|
||||
{
|
||||
if (g_list) {
|
||||
meow_listview_set_count(g_list, g_st.track_ids_count); /* 全量行数(占位行兜底未物化区) */
|
||||
if (reset_scroll) { meow_listview_set_scroll(g_list, 0); meow_listview_set_sel(g_list, -1); }
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
const ncm_playlist_t *pcur = pl_cur();
|
||||
if (g_tb && pcur) {
|
||||
@@ -261,30 +270,24 @@ static void *playlist_worker(void *arg)
|
||||
(void)arg;
|
||||
/* 拷贝打开快照 + 任务参数:单飞(g_plthr_on)保证 worker 运行期间 g_tgt 不被改写。 */
|
||||
ncm_playlist_t tpl;
|
||||
int tsearch, tidx, tgen, first, lo, hi;
|
||||
int tsearch, tidx, tgen, first, lo, hi, page;
|
||||
pthread_mutex_lock(&g_plmu);
|
||||
tpl = g_tgt.pl; tsearch = g_tgt.is_search;
|
||||
tidx = g_tgt.idx; tgen = g_tgt.gen;
|
||||
first = g_ens_first; lo = g_ens_lo; hi = g_ens_hi;
|
||||
first = g_ens_first; lo = g_ens_lo; hi = g_ens_hi; page = g_ens_page;
|
||||
pthread_mutex_unlock(&g_plmu);
|
||||
int cur_active = g_st.active_playlist; /* ensure 路径结束校验用(当前目标) */
|
||||
int rc;
|
||||
if (g_host_mock) rc = 0;
|
||||
else if (first) {
|
||||
/* 首屏:快照式加载(detail 全量 trackIds + 物化首段)。ctx 校验代次/目标,过期自动丢弃。 */
|
||||
/* 首屏:快照式加载(detail 全量 trackIds + 物化第 0 页)。ctx 校验代次/目标,过期自动丢弃。
|
||||
* §懒加载:首屏只拉一页(≤NCM_SONG_BATCH 首,一次仅一批)——不再 ensure_range 补满整个
|
||||
* 滑动窗口(旧版首屏连拉 ≤3 批:5k+ 大歌单「我喜欢的音乐」长时间卡住不出列表,连续
|
||||
* 请求还触发风控);后续页由 pl_maybe_ensure 在用户滑到已分页底部时逐页懒加载。 */
|
||||
if (tidx < 0) rc = NCM_ERR_ARG;
|
||||
else rc = appstate_load_playlist_songs_ctx(&tpl, tsearch, tgen, tidx, 0, NCM_SONG_BATCH);
|
||||
/* §22 整张歌单逐渐物化:首屏成功后把「能装进滑动窗口」的歌单一次性补填入窗
|
||||
* (≤NCM_SONG_WINDOW 的歌单整张物化,末叶行不再恒「.../加载中」——真机反馈
|
||||
* 「曲目稍多时行一直加载中,点开播放这首才回填」);超窗口的歌单仍按视口滑动补载
|
||||
* (内存纪律:窗口 ≤256 首)。窗口基址=0 覆盖 [0,min(track,WINDOW)),不滑窗不挤占视口。 */
|
||||
if (rc == NCM_OK) {
|
||||
int full = (g_st.track_ids_count < NCM_SONG_WINDOW) ? g_st.track_ids_count : NCM_SONG_WINDOW;
|
||||
if (full > NCM_SONG_BATCH) {
|
||||
int r2 = appstate_song_ensure_range(0, full);
|
||||
if (r2 != NCM_OK && rc == NCM_OK) rc = r2; /* 补填失败非致命:已物化槽保留 */
|
||||
}
|
||||
}
|
||||
if (rc == NCM_OK)
|
||||
g_paged_hi = (g_st.track_ids_count < NCM_SONG_BATCH) ? g_st.track_ids_count : NCM_SONG_BATCH;
|
||||
/* REPORT §30(真机四连症状①):首屏结果打日志——tidx/track_ids_count/物化数/rc,
|
||||
* 供「162 首歌单所有条目占位 加载中」定位(首屏 song_detail 失败/部分失败=非致命
|
||||
* g_ens_err=rc,窗口空 → 行占位;g_st.song_count 为诊断竞态读,仅日志用途)。 */
|
||||
@@ -292,8 +295,10 @@ static void *playlist_worker(void *arg)
|
||||
"playlist: first-screen idx=%d plid=%s trackIds=%d materialized=%d rc=%d",
|
||||
tidx, tpl.id, g_st.track_ids_count, g_st.song_count, rc);
|
||||
} else {
|
||||
/* 滑窗补载:物化视口区间(盘缓存优先,批网格补载)。 */
|
||||
/* 补载:物化目标区间(顺序下一页 / 回滚补物化;盘缓存优先,批网格补载)。
|
||||
* 顺序下一页成功 → 已分页高水位推进到 hi;部分失败不推进(占位行保留,滑到底部重试本页)。 */
|
||||
rc = (lo < hi) ? appstate_song_ensure_range(lo, hi) : NCM_OK;
|
||||
if (rc == NCM_OK && page && hi > g_paged_hi) g_paged_hi = hi;
|
||||
}
|
||||
pthread_mutex_lock(&g_plmu);
|
||||
if (first) {
|
||||
@@ -324,7 +329,25 @@ static void *playlist_worker(void *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 视口驱动滑窗补载:视口 ±24 行有未物化槽位 → 发 ensure worker(已在载/失败态/宿主 mock 不发)。 */
|
||||
/* 发补载 worker(单飞;调用方先判 g_plthr_on)。page=1 为顺序下一页任务。 */
|
||||
static void pl_ensure_post(int lo, int hi, int page)
|
||||
{
|
||||
g_ens_lo = lo; g_ens_hi = hi;
|
||||
g_ens_first = 0;
|
||||
g_ens_page = page;
|
||||
g_plloading = 1;
|
||||
g_plthr_on = 1;
|
||||
if (ncm_thread_create(&g_plthr, playlist_worker, NULL) != 0) {
|
||||
g_plthr_on = 0; g_plloading = 0; ncm_snack("加载失败");
|
||||
}
|
||||
}
|
||||
|
||||
/* 懒加载分页触发(Bug 修①:旧版视口 ±24 触发 + 首屏连拉多批 → 大歌单连续拉多页、阻塞 + 风控):
|
||||
* ① 顺序下一页:仅在视口底滑到「已分页底部」(余量 ≤NCM_PL_PAGE_EDGE 首)时拉下一页,
|
||||
* 一次只拉一页(≤NCM_SONG_BATCH);g_paged_hi 达 track_ids_count = 全部拉完,停止。
|
||||
* 远离已分页底部(快速猛滑到深处占位区)不拉——防连续拉多页触发风控。
|
||||
* ② 回滚补物化:视口 ±24 落在「已分页区间内」的失效槽(窗口挤出)按旧逻辑补——
|
||||
* 盘缓存(songmeta)命中为主,不消耗网络分页额度;区间外(未分页)一律不预拉。 */
|
||||
static void pl_maybe_ensure(void)
|
||||
{
|
||||
if (g_plthr_on || g_host_mock) return;
|
||||
@@ -334,23 +357,32 @@ static void pl_maybe_ensure(void)
|
||||
int scroll = meow_listview_scroll(g_list);
|
||||
int first = scroll / APP_ROW_H;
|
||||
int last = (scroll + r_list.h) / APP_ROW_H + 1;
|
||||
if (last > total) last = total;
|
||||
int paged = g_paged_hi;
|
||||
if (paged > total) paged = total; /* 防御:索引重建后高水位越界 */
|
||||
|
||||
/* ① 顺序下一页(懒加载) */
|
||||
if (paged < total) {
|
||||
int margin = paged - last; /* 视口底距已分页底部余量(首;负=已越过) */
|
||||
if (margin <= NCM_PL_PAGE_EDGE && margin >= -NCM_PL_PAGE_EDGE) {
|
||||
int hi = paged + NCM_SONG_BATCH;
|
||||
if (hi > total) hi = total;
|
||||
pl_ensure_post(paged, hi, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* ② 回滚补物化(仅已分页区间内) */
|
||||
int lo = first - 24, hi = last + 24;
|
||||
if (lo < 0) lo = 0;
|
||||
if (hi > total) hi = total;
|
||||
if (hi > paged) hi = paged;
|
||||
if (lo >= hi) return;
|
||||
if (appstate_range_loaded(lo, hi)) return; /* 已物化:无操作(窗口原地不动) */
|
||||
g_ens_lo = lo; g_ens_hi = hi;
|
||||
g_ens_first = 0;
|
||||
g_plloading = 1;
|
||||
g_plthr_on = 1;
|
||||
if (ncm_thread_create(&g_plthr, playlist_worker, NULL) != 0) {
|
||||
g_plthr_on = 0; g_plloading = 0; ncm_snack("加载失败");
|
||||
}
|
||||
pl_ensure_post(lo, hi, 0);
|
||||
}
|
||||
|
||||
void playlist_refresh(void)
|
||||
{
|
||||
if (g_host_mock) { g_load_err = 0; g_ens_err = 0; pl_apply(1); return; }
|
||||
if (g_host_mock) { g_load_err = 0; g_ens_err = 0; g_paged_hi = g_st.track_ids_count; pl_apply(1); return; }
|
||||
if (g_plthr_on) return;
|
||||
g_load_err = 0;
|
||||
g_ens_err = 0;
|
||||
@@ -373,7 +405,9 @@ void playlist_refresh(void)
|
||||
if (!appstate_playlist_window_is(g_tgt.pl.id))
|
||||
appstate_playlist_reset();
|
||||
|
||||
g_ens_first = 1; /* 首屏:重建索引 + 物化首段 */
|
||||
g_ens_first = 1; /* 首屏:重建索引 + 物化第 0 页 */
|
||||
g_ens_page = 0;
|
||||
g_paged_hi = 0; /* 分页高水位归零:首屏成功后由 worker 重标 */
|
||||
g_pl_stale = 0;
|
||||
g_plloading = 1;
|
||||
g_plthr_on = 1;
|
||||
|
||||
Reference in new issue
Block a user