Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/lyric_sim.c
T
AskaEth 7c7d891570 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 与容器一致)。
2026-11-04 20:02:28 +08:00

146 lines
6.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* bench/lyric_sim.c — 歌词「掉线失败 → 重连 → 重试成功」宿主离线回归(0 真实请求)。
*
* 驱动真实 app/play/player_manager.c 的 lyric_worker(后台歌词线程),网络换成可注入的
* ncm_api_lyric 桩:第 1 次调用返回 NCM_ERR_NET(模拟掉线瞬间歌词请求失败),第 2 次返回
* NCM_OK + 一段 LRC(模拟网络恢复后重试成功)。
*
* 复现的真机症状(REPORT/任务):播放中歌词正常;网络掉线一会儿再重连后某曲恒「暂无歌词」。
* 根因:旧 lyric_worker 在**发起网络请求前**就把 last_id 记为已拉,且失败也置 lyric_state=2
* (=已拉完),故网络失败被写死成「无歌词」,重连后永不重试。本机回归:
* ① 第 1 次失败后 lyric_state() 必须**不是** 2(可重试),且无词(lyric_count()==0);
* ② 推进单调时钟越过退避点(模拟重连)后必须**自动重试**并成功(第 2 次调用)+ 词展示
* (lyric_count()>0、lyric_state()==2)。
* 旧代码在 ② 上 FAIL(g_lyr_state 恒 2、第 2 次调用永不发生)。
*
* 编译(宿主,ncm_app 目录下;gc-sections 只保留歌词路径,避免链整个播放链的符号):
* CC=gcc F="-ffunction-sections -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd -Ivendor/jsmn"
* $CC $F -c bench/lyric_sim.c -o /tmp/ly.o
* $CC $F -c app/play/player_manager.c -o /tmp/ly_pm.o
* $CC $F -c app/logic/lyric.c -o /tmp/ly_lrc.o
* $CC -Wl,--gc-sections -o lyric_sim /tmp/ly.o /tmp/ly_pm.o /tmp/ly_lrc.o -lpthread -lm
* 运行:./lyric_sim (PASS/FAIL 退出码)
*/
#include "../app/ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h> /* usleep */
/* ---- 全局状态(app.c 定义;本 sim 提供桩) ---- */
ncm_app_state_t g_st;
ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER, .state = NCM_PC_STOPPED, .cur_song = -1 };
volatile int g_app_quit = 0;
volatile int g_net_ready = 0;
int g_host_mock = 1;
int g_dark = 0;
/* ---- 可控单调时钟(lyric_worker 用 ncm_monotonic_ms 做失败退避) ---- */
static int64_t g_now_ms = 1000;
int64_t ncm_monotonic_ms(void) { return g_now_ms; }
size_t ncm_strlcpy(char *dst, const char *src, size_t sz)
{
size_t n = 0;
if (sz) { while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; } dst[n] = 0; }
while (src[n]) n++;
return n;
}
void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; }
/* appstate 桩:单曲队列(idx 0 恒有效,song id=1000) */
int appstate_queue_count(void) { return 1; }
/* page-cache 治理桩:恒非暂停(不挡歌词预取) */
int meow_cachegov_prefetch_paused(void) { return 0; }
/* 媒体会话歌词上报桩:失败上报空、成功上报 LRC;计数供断言 */
static int g_report_empty;
static int g_report_lyric;
static char g_report_last_lrc[256];
void ipc_media_lyric_report(const char *lrc, const char *tl)
{
(void)tl;
if (lrc && lrc[0]) { g_report_lyric++; ncm_strlcpy(g_report_last_lrc, lrc, sizeof g_report_last_lrc); }
else { g_report_empty++; }
}
/* 歌词 API 桩:第 1 次=网络失败,其后=成功返回 LRC。 */
static int g_fetch_calls;
int ncm_api_lyric(const char *device_id, const char *cookie, const char *id,
char *lrc, size_t lrc_sz, char *tlrc, size_t tlrc_sz)
{
(void)device_id; (void)cookie; (void)id; (void)tlrc; (void)tlrc_sz;
g_fetch_calls++;
if (g_fetch_calls == 1) {
if (lrc && lrc_sz) lrc[0] = 0; /* 掉线失败:不写词 */
return NCM_ERR_NET; /* 网络错误(掉线) */
}
static const char body[] =
"[00:00.00]Line one\n[00:05.00]Line two\n[00:10.00]Line three\n";
if (lrc && lrc_sz) snprintf(lrc, lrc_sz, "%s", body);
return NCM_OK;
}
static void wait_ms(int ms)
{
for (int i = 0; i < ms; i += 20) {
usleep(20000);
if (g_app_quit) break;
}
}
static int wait_until(const volatile int *val, int target, int timeout_ms)
{
for (int i = 0; i < timeout_ms; i += 10) {
if (*val >= target) return 1;
usleep(10000);
}
return (*val >= target);
}
int main(void)
{
memset(&g_st, 0, sizeof g_st);
pthread_mutex_init(&g_play.mu, NULL);
g_play.state = NCM_PC_PLAYING;
g_play.cur_song = 0;
g_play.play_song_valid = 1;
ncm_strlcpy(g_play.play_song.id, "1000", sizeof g_play.play_song.id);
if (player_manager_load_lyric() != NCM_OK) { printf("FAIL start worker\n"); return 1; }
int ok = 1;
/* ① 第 1 次拉取失败(网络错误):须可重试(state!=2)、无词 */
if (!wait_until(&g_fetch_calls, 1, 3000)) { printf("FAIL first fetch never happened\n"); return 1; }
wait_ms(400); /* 让 worker 完成失败收尾(state=0, retry_at 已算) */
int st1 = lyric_state();
int n1 = lyric_count();
int ok1 = (g_fetch_calls == 1 && st1 != 2 && n1 == 0);
printf("step1 failure: calls=%d state=%d (expect !=2) count=%d rep_empty=%d\n",
g_fetch_calls, st1, n1, g_report_empty);
printf(" -> %s\n", ok1 ? "PASS" : "FAIL");
ok &= ok1;
/* 模拟网络重连:推进单调时钟越过失败退避点 */
g_now_ms = 10000;
/* ② 重试自动发生并成功:第 2 次调用、state=2、词已展示 */
if (!wait_until(&g_fetch_calls, 2, 4000)) {
printf("FAIL retry never fired (calls=%d) — old code keeps state=2, no retry\n", g_fetch_calls);
return 1;
}
int st2 = lyric_state();
int n2 = lyric_count();
int ok2 = (g_fetch_calls == 2 && st2 == 2 && n2 > 0 && g_report_lyric >= 1);
printf("step2 retry: calls=%d state=%d count=%d rep_lyric=%d lrc=%s\n",
g_fetch_calls, st2, n2, g_report_lyric, g_report_last_lrc);
printf(" -> %s\n", ok2 ? "PASS" : "FAIL");
ok &= ok2;
g_app_quit = 1;
printf("lyric_sim overall -> %s\n", ok ? "PASS" : "FAIL");
fflush(stdout);
return ok ? 0 : 1;
}