用户定案:失效前用当前 cookie 换新登录态(用户无感);仅刷新失败才退扫码重登。 - core/ncm_req.c|h:eapi Cookie 头补齐会话锚点 MUSIC_U/MUSIC_A(对齐 request.rs, 补 §36.5 遗留——登录刷新/song_url 会员档依赖);新增 ncm_req_cookie_merge (patch 覆盖 base 同名 cookie,MUSIC_A/__remember_me 等不丢)。 - core/ncm_login.c|h:ncm_login_refresh(eapi /api/login/token/refresh 空体, code200+set-cookie→merge;code!=200→NCM_ERR_AUTH 退扫码;网络/TLS→负码退避)。 - app/logic/account.c:account.json 增 last_refresh_ts + 拆分 cvip/svip; account_cookie_maybe_refresh(年龄阈值 12h 可配 ncm.cookie_refresh_h,单飞/boot, 失败冷却 30min 跨 boot 持久化,AUTH 置回登录页标志)+ mark_fresh/need_relogin。 - app/ui/home.c:home_worker 先续期再拉歌单;app/ui/login.c:803 落盘后 mark_fresh; app/app.c:UI tick 消费回登录标志 → logout+切登录页。 - 新增测试:test_ncm_core 的 cookie_merge + eapi_musicu_hdr;bench/cookie_refresh_sim (端点语义,0 网络);bench/account_cookie_sim(临期/未临期/退避/失效/冷却,0 网络)。 验证:host+qemu 核心测试 PASS;build_ncm_sims.sh 全家桶 PASS;build_ncm.sh 0 warning。 真机只读探针 2 次(设备 10.78.193.50,不写回):refresh 走 eapi 域 interface.music.163.com 在当前网络被 TLS reset(alert 80)→ rc=-5 瞬断退避, session.enc/deviceId md5 前后一致(登录态零损伤)。新 ncm md5 c960244b。
222 lines
9.5 KiB
C
222 lines
9.5 KiB
C
/* app.c — dev.player.ncm 应用装配层:main + 组装 + 事件分发。
|
||
*
|
||
* 职责(对齐 hello_app/app.c):main 只做初始化/建页/跑事件循环/清理;把 meow_ui_run 的平铺输入
|
||
* 回调(key/tap/swipe/tick/touch)路由到当前活动页。页面切换统一走 meow_view_page_push/pop/replace。
|
||
* 播放线程(player_manager)+ 媒体会话 IPC 线程(ipc_media)在 main 起动、退出时停。
|
||
*
|
||
* 铁律(APPS.md §3.4.1):物理键禁映射——on_key 不处理任何键语义;返回=左缘左滑/返回钮/request_exit。
|
||
* 左缘返回:播放页→歌单→主页→退出(登录页→退出)。
|
||
*/
|
||
#include "ncm_app_internal.h"
|
||
#include <signal.h>
|
||
#include <ucontext.h>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
|
||
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 = 0;
|
||
|
||
static meow_view *g_login_view, *g_home_view, *g_pl_view, *g_plr_view, *g_set_view,
|
||
*g_search_view;
|
||
|
||
static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); }
|
||
|
||
/* SIGSEGV/SIGBUS/SIGILL 定位处理器(真机复现调试用):**必须 async-signal-safe**(只 write+_exit,
|
||
* 手写十六进制格式化,不用 snprintf/sprintf——后者内部取锁,在信号上下文可能自锁死)。把
|
||
* si_addr + 中断点 pc + ra/sp/fp(MIPS O32:mcontext_t.pc / gregs[31 ra][29 sp][30 fp])
|
||
* 写 stderr(amsd 捕获)+ 固定文件,配合 nm/链接布局定位崩溃函数。 */
|
||
static void fatal_sig_report(int sig, siginfo_t *si, void *ctx)
|
||
{
|
||
ucontext_t *uc = (ucontext_t *)ctx;
|
||
unsigned long long v = 0;
|
||
unsigned char b[192];
|
||
int o = 0;
|
||
const char *tag = (sig == SIGSEGV) ? "SIGSEGV" : (sig == SIGBUS ? "SIGBUS" : "SIGILL");
|
||
#define FATAL_PUTS(s) do { const char *_p = (s); while (*_p) b[o++] = (unsigned char)*_p++; } while (0)
|
||
#define FATAL_HEX(x) do { unsigned long long _v = (unsigned long long)(x); \
|
||
int _sh; \
|
||
for (_sh = 28; _sh >= 0; _sh -= 4) b[o++] = \
|
||
(unsigned char)"0123456789abcdef"[(_v >> _sh) & 15]; } while (0)
|
||
#define FATAL_STR(h, x) do { FATAL_PUTS(h); FATAL_HEX(x); } while (0)
|
||
FATAL_PUTS("\nFATAL "); FATAL_PUTS(tag);
|
||
FATAL_PUTS(" sig="); FATAL_HEX(sig);
|
||
v = (unsigned long long)(uintptr_t)si->si_addr; FATAL_STR(" addr=", v);
|
||
v = uc->uc_mcontext.pc; FATAL_STR(" pc=", v);
|
||
v = uc->uc_mcontext.gregs[31]; FATAL_STR(" ra=", v);
|
||
v = uc->uc_mcontext.gregs[29]; FATAL_STR(" sp=", v);
|
||
v = uc->uc_mcontext.gregs[30]; FATAL_STR(" fp=", v);
|
||
FATAL_PUTS("\n");
|
||
#undef FATAL_PUTS
|
||
#undef FATAL_HEX
|
||
#undef FATAL_STR
|
||
(void)write(2, b, (size_t)o);
|
||
int fd = open("/usr/data/mnt/sd_0/MeowOS/logs/dev.player.ncm/crash_report.txt",
|
||
O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
if (fd >= 0) { (void)write(fd, b, (size_t)o); close(fd); }
|
||
_exit(128 + sig);
|
||
}
|
||
|
||
static void fatal_sig_install(void)
|
||
{
|
||
struct sigaction sa;
|
||
memset(&sa, 0, sizeof sa);
|
||
sa.sa_sigaction = fatal_sig_report;
|
||
sa.sa_flags = SA_SIGINFO; /* 不置 SA_NODEFER:防信号内重入 */
|
||
sigemptyset(&sa.sa_mask);
|
||
sigaction(SIGSEGV, &sa, NULL);
|
||
sigaction(SIGBUS, &sa, NULL);
|
||
sigaction(SIGILL, &sa, NULL);
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 事件分发:meow_ui_run 平铺回调 → 当前页 */
|
||
/* ============================================================ */
|
||
|
||
static void app_on_key(int keycode, int is_down)
|
||
{
|
||
(void)keycode; (void)is_down; /* 物理键系统语义;App 不映射(§3.4.1) */
|
||
}
|
||
|
||
static void app_on_tap(int x, int y)
|
||
{
|
||
if (login_is_active()) { login_on_tap(x, y); return; }
|
||
if (player_is_active()) { player_on_tap(x, y); return; }
|
||
if (settings_is_active()) { settings_on_tap(x, y); return; }
|
||
if (playlist_is_active()) { playlist_on_tap(x, y); return; }
|
||
if (search_is_active()) { search_on_tap(x, y); return; }
|
||
home_on_tap(x, y);
|
||
}
|
||
|
||
static void app_on_swipe(int dir, int dx, int dy)
|
||
{
|
||
(void)dx;
|
||
if (dir == MEOW_SWIPE_LEFT) {
|
||
/* 搜索历史模式:左滑删单条(§23);未消费才走上/下一层返回语义。 */
|
||
if (search_is_active() && search_swipe_left()) return;
|
||
if (player_is_active()) { meow_view_page_pop(); return; }
|
||
if (settings_is_active()) { meow_view_page_pop(); return; }
|
||
if (playlist_is_active()) { meow_view_page_pop(); return; }
|
||
if (search_is_active()) { meow_view_page_pop(); return; }
|
||
/* home / login = 根 → 退出 */
|
||
meow_log(MEOW_LOG_INFO, "exit:swipe");
|
||
meow_ui_request_exit();
|
||
return;
|
||
}
|
||
if (home_is_active()) { home_swipe(dir, dy); return; }
|
||
if (playlist_is_active()) { playlist_swipe(dir, dy); return; }
|
||
if (search_is_active()) { search_swipe(dir, dy); return; }
|
||
if (player_is_active()) { player_on_swipe(dir, dy); return; }
|
||
}
|
||
|
||
static void app_on_touch_down(int x, int y)
|
||
{
|
||
if (player_is_active()) player_touch_down(x, y); /* §22 中部横滑页切换跟踪 */
|
||
else if (home_is_active()) home_touch_down(x, y);
|
||
else if (playlist_is_active()) playlist_touch_down(x, y);
|
||
else if (settings_is_active()) settings_touch_down(x, y);
|
||
else if (search_is_active()) search_touch_down(x, y);
|
||
}
|
||
|
||
static void app_on_touch_move(int x, int y)
|
||
{
|
||
if (player_is_active()) player_touch_move(x, y);
|
||
else if (home_is_active()) home_touch_move(x, y);
|
||
else if (playlist_is_active()) playlist_touch_move(x, y);
|
||
else if (settings_is_active()) settings_touch_move(x, y);
|
||
else if (search_is_active()) search_touch_move(x, y);
|
||
}
|
||
|
||
static void app_on_swipe_progress(int x_start, int dx)
|
||
{
|
||
meow_ui_swipe_hint(x_start + dx);
|
||
}
|
||
|
||
static void app_on_tick(void)
|
||
{
|
||
uint64_t now = meow_ui_time_ms();
|
||
/* §NEW cookie 续期判会话失效 → 清登录态回登录页(worker 在后台续期发现 301/-460 时置标志;
|
||
* 在此 UI 线程执行 logout+切页——meow_view_page_replace 非线程安全,只能 UI 线程做) */
|
||
if (account_cookie_need_relogin()) {
|
||
account_cookie_relogin_consume();
|
||
meow_log(MEOW_LOG_WARN, "account: cookie invalid -> re-login");
|
||
appstate_logout();
|
||
meow_view_page_replace(ACT_LOGIN);
|
||
}
|
||
autoplay_maybe(); /* 真机联调 hook:/tmp/ncm_autoplay 存在则自动播已缓存曲目(一次性) */
|
||
/* autoplay 已在后台线程起播缓存曲 → 在此(UI 线程)打开播放页,复现用户点曲播放路径 */
|
||
{
|
||
int ai = autoplay_ui_consume();
|
||
if (ai >= 0) player_play_song(ai);
|
||
}
|
||
if (login_is_active()) { login_tick(now); return; }
|
||
if (player_is_active()) { player_tick(now); return; }
|
||
if (search_is_active()) { search_tick(now); return; }
|
||
if (home_is_active()) { home_tick(now); }
|
||
if (playlist_is_active()) { playlist_tick(now); }
|
||
chrome_tick();
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* main */
|
||
/* ============================================================ */
|
||
int main(void)
|
||
{
|
||
meow_log(MEOW_LOG_INFO, "start");
|
||
meow_ui_opts o = { APP_W, APP_H };
|
||
if (meow_ui_init(&o) != 0) { meow_log(MEOW_LOG_ERROR, "init failed"); return 1; }
|
||
/* 主题侦听:登记真实回调(旧代码 pass NULL 无效),系统明暗切换 → ncm_on_theme 实时换肤。
|
||
* 初始同步一次 g_dark(listener 只在变化时被调,初次状态需显式对齐)。 */
|
||
g_dark = (meow_theme_get_mode() == MEOW_THEME_DARK);
|
||
meow_theme_add_listener(ncm_on_theme);
|
||
signal(SIGTERM, on_sigterm);
|
||
fatal_sig_install(); /* SIGSEGV/SIGBUS/SIGILL 写崩溃地址定位(真机复现调试) */
|
||
|
||
/* 应用数据/核心库上下文(cookie/deviceId/cache/设置) */
|
||
appstate_init();
|
||
/* M2.82-联调修复:设备无系统 CA 库(真机 rc=-5 TLS 错误根因)——App 自带 Mozilla CA bundle,
|
||
* 显式指给 core HTTP 层(保持 verify=1,不降级为不校验) */
|
||
ncm_http_set_verify_default(1, "/usr/data/mnt/sd_0/MeowAPPs/dev.player.ncm/cacert.pem");
|
||
|
||
/* 播放 + 媒体会话线程(UI 不卡;无 launcher socket 时 ipc 优雅跳过) */
|
||
player_manager_start();
|
||
ipc_media_start();
|
||
g_net_ready = 1;
|
||
|
||
/* 建六页 view */
|
||
g_login_view = login_view_create();
|
||
g_home_view = home_view_create();
|
||
g_pl_view = playlist_view_create();
|
||
g_plr_view = player_view_create();
|
||
g_set_view = settings_view_create();
|
||
g_search_view = search_view_create();
|
||
|
||
/* 进入根页:已登录→home,否则→login */
|
||
meow_view_page_push(g_st.logged_in ? ACT_HOME : ACT_LOGIN);
|
||
|
||
meow_ui_events ev = {
|
||
app_on_key, app_on_tap, app_on_swipe, app_on_tick, app_on_swipe_progress,
|
||
app_on_touch_down, app_on_touch_move,
|
||
};
|
||
meow_ui_run(&ev);
|
||
|
||
/* 清理:停线程 → 释放页 */
|
||
g_app_quit = 1;
|
||
player_manager_stop_thread();
|
||
ipc_media_stop();
|
||
meow_view_free(g_set_view);
|
||
meow_view_free(g_plr_view);
|
||
meow_view_free(g_pl_view);
|
||
meow_view_free(g_home_view);
|
||
meow_view_free(g_login_view);
|
||
meow_view_free(g_search_view);
|
||
meow_ui_shutdown();
|
||
meow_log(MEOW_LOG_INFO, "exit");
|
||
return 0;
|
||
}
|