Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/ncm_app_internal.h
T
MeowOS 820d1e52bf fix(media): 物理播放/暂停键(164)改发 PLAYCTL cmd=6 toggle + App 前台状态栏音量%实时刷新
Bug1: SDK meow_ui_media 按 g_last_state 本地判 play/pause,但 ncm 走自有 ipc_media
线程不经 SDK meta 上报 → g_last_state 恒 0 → 恒发 cmd=1(播放),播放中按键无效。
修:SDK 164 恒发 cmd=6 toggle(删 g_last_state 判定),ncm apply_playctl case 6
按 player_manager_state() 当前态翻转(源是状态权威);launcher meow_media/util
本就透传 cmd 不动结构,仅 meow_ipc.h/SPEC_REF 注释补 6=toggle。媒体卡片/NP 页
按钮(按 launcher 侧 s->state 判,来自 META 是准的)维持现状。

Bug2: App 前台时状态栏为 launcher 叠加层,App 内 SDK 改音量/亮度只写
settings.conf+DAC,launcher meow_settings 内存表不感知 → g_sys.vol 停旧值。
修:app_overlay_statusbar 前插 app_overlay_settings_poll——1s 节流 stat
settings.conf,mtime 变才 meow_settings_init 重读+sync 缓存+g_sys.vol/brightness
(meow_settings 新增 meow_settings_file_path() 访问器防硬编码分叉)。

验证:宿主 test_sdk_input_keys 19/19(K4 改 toggle 断言)、test_media +cmd=6
透传用例 ALL PASS、ncm bench/toggle_sim 10/10(新)、player_flow/cache_evict
sim PASS、ncm core 宿主+qemu-mipsel PASS、test_settings_brightness PASS;
mipsel 交叉 -Wall -Wextra 0 warning(改动文件单独验+整构建)。
真机(0.7.91):ncm 热更(.new+mv)后 autoplay(Docu) 起播缓存曲 → meow_ipc
playctl 6 二连发 → paused(pos=139691)/resumed 日志实证 toggle 全链路。
md5:ncm 8f10eee5 / meow_ui.a 7b359ec9(gitignore)/ launcher eba4f2b2
(/usr/bin 只读,fw_content.conf 已更新待固件)。物理键 164 实体按压留用户终验。
2026-09-01 14:44:55 +08:00

358 lines
20 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.
/* ncm_app_internal.h — dev.player.ncm 应用内部共享头(UI 各页 + app 装配层 + 宿主测试面)。
*
* 层级(对齐 hello_app/app.c 批② + files_app):
* - app.c main + 装配 + 事件分发(把 meow_ui_run 的平铺回调路由到各页)
* - app/ui/<page>.c 每页一个 view 文件(login/home/playlist/player/settings)
* - app/ui/common/chrome.c 共享 chrome(topbar / 内容区几何 / 空态 / 提示)
* - app/logic/ appstate(全局 ctx/歌单/歌曲/播放态)+ lyric(LRC 解析) + cover(封面缓存)
* - app/play/ player_manager(播放线程) + audio_out(audiod/ALSA/文件输出) + ipc_media(媒体会话 IPC)
*
* 铁律(APPS.md §3.4.1 / WORK_STATE.md):物理键禁映射——on_key 不处理任何键语义;
* 返回=左缘左滑(on_swipe LEFT)/返回钮/meow_ui_request_exit;左缘返回:播放页→歌单→主页→退出。
* 播放/网络/IPC 用独立线程,UI 不卡。媒体会话经 meow_ipc.h 帧格式最小客户端上报
* MSG_META + 收 MSG_PLAYCTL(SDK 尚无媒体封装,App 内实现)。
*/
#ifndef NCM_APP_INTERNAL_H
#define NCM_APP_INTERNAL_H
#include <stdint.h>
#include <stddef.h>
#include <pthread.h>
#include "meow_ui.h"
#include "meow_ui_view.h"
#include "ncm.h" /* 核心库伞头(ctx/api/login/cache/...) */
/* ---- Activity 归属(§6.1 ①)---- */
#define ACT_LOGIN 0 /* 扫码登录页(根,未登录) */
#define ACT_HOME 1 /* 我的歌单(根/主页,已登录) */
#define ACT_PLAYLIST 2 /* 歌单详情(曲目列表) */
#define ACT_PLAYER 3 /* 播放页(可横竖屏切,v1 允许切) */
#define ACT_SETTINGS 4 /* 设置页 */
#define ACT_SEARCH 5 /* 搜索页(顶栏搜索按钮进入) */
/* ---- 布局 ---- */
#define APP_W 480
#define APP_H 720
#define APP_TOPBAR_H 56
#define APP_ROW_H 56 /* 列表行高 */
#define APP_PAD 16
#define APP_OP_W 48 /* 行右侧操作区触达宽 */
#define APP_FOOTER_H 26
/* ---- 数据规模(内存纪律:歌单/歌曲大列表虚拟化显示,数组有界;硬约束 R3P2 ~14MB)---- */
#define NCM_PLAYLIST_MAX 120 /* 我的歌单上限 */
/* 滑动窗口(REPORT §17,三期第三批,取代 §14 的 NCM_SONG_MAX=1000 硬截断):
* song_ids[NCM_TRACKID_MAX] = detail(/v6/playlist/detail, n=1)取回的全量 trackIds
* (int64×16384=128KB bss,支撑任意长度歌单,不再 1000 截断);
* songs[NCM_SONG_WINDOW] = 物化滑动窗口(256 首 × ~608B ≈ 152KB bss,与歌单长度无关);
* song_valid[NCM_SONG_WINDOW]= 槽位有效位图(songs[i] ↔ 全局下标 window_base+i);
* 滚动出窗的槽位失效(数据已序列化到 <cache_dir>/meta/<id>.bin),入窗经 song_detail 按需
* 拉取(盘缓存命中免请求)。列表 count = track_ids_count(全量),占位行驱动后台 ensure。 */
#define NCM_TRACKID_MAX 16384 /* 全量 trackIds 索引上限(128KB bss) */
#define NCM_SONG_WINDOW 256 /* 滑动窗口大小(内存物化歌曲对象数上限) */
#define NCM_SONG_BATCH 100 /* song_detail 分批每批条数(≤500 安全窗内取保守 100;
* 窗口补载粒度;c 数组缓冲 clist[4096] 装 100 首 ≈2KB) */
#define NCM_LYRIC_LINES 600 /* 歌词行上限 */
#define NCM_COVER_MAX 288*1024 /* 封面下载限幅 ~288KB */
#define NCM_SEARCH_MAX 32 /* 搜索结果上限(v1 只显示前 32 条,无大缓冲) */
/* ---- 播放/媒体会话状态(play 线程 ↔ UI/IPC 共享,mu 保护)---- */
enum {
NCM_PC_STOPPED = 0,
NCM_PC_PLAYING = 1,
NCM_PC_PAUSED = 2,
NCM_PC_BUFFER = 3, /* 缓冲/加载中 */
};
/* 播放器命令(IPC MSG_PLAYCTL cmd 一一对应)*/
enum {
NCM_CTL_PLAY = 1,
NCM_CTL_PAUSE = 2,
NCM_CTL_PREV = 3,
NCM_CTL_NEXT = 4,
NCM_CTL_SEEK = 5,
NCM_CTL_TOGGLE = 6, /* 播放/暂停切换(物理键 164 恒发此;按当前态翻转) */
};
/* 跨线程播放运行时状态(main 只读展示;play 线程写,IPC 线程读)。 */
typedef struct ncm_play_run {
pthread_mutex_t mu;
int state; /* NCM_PC_* */
int cur_song; /* 当前曲目在 g_st.songs[] 下标;-1=无 */
int64_t pos_ms; /* 当前播放位置(播放进程推进,seek 写此) */
int64_t dur_ms; /* 当前曲目标称时长 */
int cmd; /* 暂存最新内部命令(play/pause/prev/next/seek)*/
int64_t cmd_seek_ms; /* cmd==SEEK 目标 */
int seq; /* 播放代际(每次 start 递增,用于区分过期信号)*/
int err; /* 最近一次播放错误(0=无) */
char errmsg[96];
int64_t dl_done; /* 本曲已下载字节(缓冲期进度反馈,§18) */
int64_t dl_total; /* 本曲标称大小(song_url 返回;0=未知) */
int has_track; /* 当前是否有可播曲目(含 url 获取中) */
/* 播放曲快照(REPORT §17):run_song 起播时把歌曲对象整体拷入(mu 保护),
* 之后播放/歌词/IPC/UI 全读快照而非窗口——窗口滑出当前播放曲也不影响播放。 */
ncm_song_t play_song;
int play_song_valid; /* 1=快照有效(stop 清 0) */
} ncm_play_run_t;
/* ---- 音质档位字符串(设置页 + 持久化用)---- */
extern const char *media; /* 占位 */
/* ---- App 全局状态(main 线程拥有;play 线程经 ncm_play_run 共享)---- */
typedef struct ncm_app_state {
ncm_ctx_t ctx; /* 核心库上下文(device_id/cookie/cache) */
char data_dir[512]; /* 数据目录(MeowData/dev.player.ncm/data) */
char cache_dir[512];
/* 登录 */
char unikey[64];
char qr_url[256];
unsigned char qr_mods[177*177]; /* QR 位图矩阵(1=暗) */
int qr_size;
int login_state; /* NCM_LOGIN_* */
int logged_in;
/* 账号 */
char uid[32];
char nickname[128];
/* 我的歌单 */
ncm_playlist_t playlists[NCM_PLAYLIST_MAX];
int playlist_count;
int active_playlist; /* 当前打开歌单的下标;-1=无 */
/* 当前歌单曲目(id 用 int64_t:网易云歌曲 id 可 >2^31,MIPS32 long 溢出)。
* 滑动窗口模型(REPORT §17):
* song_ids[] = detail 返回的全量 trackIds(≤ NCM_TRACKID_MAX,全局下标索引);
* songs[] = 物化滑动窗口,songs[i] ↔ 全局下标 window_base+i;
* song_valid[] = 槽位有效位图(出窗=失效,入窗=物化后置位);
* window_base = 窗口起始全局下标;song_count = 窗口内有效槽位数(≤ NCM_SONG_WINDOW);
* track_ids_count = trackIds 总数(列表 count = track_ids_count,任意长度无截断)。
* 窗口读写由 appstate.c 内 g_song_mu 保护;外部一律走 appstate_song_copy/ensure_range。 */
int64_t song_ids[NCM_TRACKID_MAX];
ncm_song_t songs[NCM_SONG_WINDOW];
uint8_t song_valid[NCM_SONG_WINDOW];
int window_base;
int song_count;
int track_ids_count; /* detail 取回的 trackIds 总数(≤ NCM_TRACKID_MAX) */
/* 播放页视图态 */
int playing_song; /* 正在播放歌曲在 songs[] 下标(UI 侧缓存);-1=无 */
meow_img *cover_img; /* 当前封面(解码后;UI 持有,play 线程不碰) */
char cover_url[512]; /* 当前封面 URL(playlist/song cover) */
/* 歌词(播放器加载后由 play 线程填充,UI 读)*/
pthread_mutex_t lyric_mu;
int lyric_line_count;
int cur_lyric_line;
/* 设置(持久化 MeowData/dev.player.ncm/data)*/
int level; /* NCM_LEVEL_* */
int cache_limit_gb;
/* 搜索(结果复用 g_st.songs[] 浅拷贝区;仅搜索页上下文有效)*/
char search_query[256];
int search_count; /* 当前搜索结果数(含 0=无/未搜) */
volatile int search_loading; /* 1=搜索请求在途 */
int search_err; /* 最近一次搜索错误(0=ok) */
} ncm_app_state_t;
extern ncm_app_state_t g_st; /* 全局状态(app.c 定义) */
extern ncm_play_run_t g_play; /* 播放运行态(app.c 定义) */
extern volatile int g_app_quit; /* 请求退出(线程停) */
extern volatile int g_net_ready; /* 网络/核心库线程安全可用 */
extern int g_host_mock; /* 1=宿主/测试:跳过网络,用预置数据 */
/* ---- App 侧调色板(ui/common/ncm_colors.c)----
* 品牌红 + 播放页/登录专属色。中性色由 meow_theme_get() 语义色提供。§3.2/§3.3/§5.4。 */
typedef struct ncm_pal {
uint32_t red; /* 主 accent:day #C20C0C / night #EC4141 */
uint32_t red_lit; /* 亮点变体:day #EC4141 / night #FF6A6A */
uint32_t red_ctr; /* 红底浅块:day #FBE9E9 / night #3A1919 */
uint32_t on_red; /* 红底前景(#FFFFFF) */
uint32_t on_red_dim; /* 红底次前景(白 ~88%,文本用) */
uint32_t hero; /* 登录 hero 深红压场:day #C20C0C / night #7A0E0E */
uint32_t player_bg; /* 播放页沉浸底:day #FFFDF9 / night #0C0D10 */
uint32_t disc; /* 黑胶盘体:day #1A1A1A / night #141414 */
uint32_t disc_edge; /* 盘缘槽纹:day #2A2A2A / night #232323 */
} ncm_pal_t;
extern int g_dark; /* 1=黑红(night) 0=白红(day) */
const ncm_pal_t *ncm_palette(int dark); /* dark==g_dark */
void ncm_on_theme(meow_theme_mode m); /* 主题侦听:明暗变化 → 整 App 重绘 */
void ncm_fill_round(int x, int y, int w, int h, int r, uint32_t color); /* 填充圆角矩形 */
void ncm_draw_button(int x, int y, int w, int h, const char *label,
uint32_t bg, uint32_t fg); /* App 侧品牌红/FILLED 按钮 */
int first_glyph(const char *s, char *out, size_t sz); /* 取首个 UTF-8 字形(头像首字) */
const char *ncm_text_fit(const char *s, int maxw, int size); /* 按宽裁剪("..."结尾;静态缓冲) */
/* ---- 通用矩形 ---- */
typedef struct { int x, y, w, h; } ncm_rect_t;
#define NR(x,y,w,h) ((ncm_rect_t){ (x),(y),(w),(h) })
/* ---- chrome(ui/common/chrome.c)---- */
int ncm_statusbar(void);
ncm_rect_t ncm_topbar_rect(void);
ncm_rect_t ncm_content_rect(void);
meow_widget *ncm_topbar_create(const char *title, int back, const char *action_label);
void ncm_draw_empty(ncm_rect_t r, const char *kind, const char *what);
void ncm_draw_footer(ncm_rect_t r, const char *text);
void ncm_snack(const char *text); /* 底部 2s 提示条(非阻塞) */
void chrome_tick(void); /* 周期检查 snack 到期清除 */
/* ---- 页面(ui/<page>.c)---- */
/* login */
meow_view *login_view_create(void);
int login_is_active(void);
void login_on_tap(int x, int y);
void login_tick(uint64_t now);
int login_start(void); /* 触发 unikey 获取(宿主测试:无网络则返回) */
const char *login_status_str(int state);
/* home */
meow_view *home_view_create(void);
int home_is_active(void);
int home_on_tap(int x, int y);
void home_swipe(int dir, int dist);
void home_touch_down(int x, int y);
void home_touch_move(int x, int y);
void home_reload(void); /* 刷新我的歌单(后台 worker,非阻塞) */
void home_tick(uint64_t now); /* UI tick:检查歌单 worker 是否完成并刷新 */
int home_go_up(void); /* 左缘返回:到底则退出 */
/* playlist */
meow_view *playlist_view_create(void);
int playlist_is_active(void);
int playlist_on_tap(int x, int y);
void playlist_swipe(int dir, int dist);
void playlist_touch_down(int x, int y);
void playlist_touch_move(int x, int y);
void playlist_open(int idx); /* 打开第 idx 个歌单(下拉进歌单详情) */
void playlist_refresh(void);
void playlist_tick(uint64_t now);/* UI tick:检查歌单 worker 是否完成并刷新 */
/* player */
meow_view *player_view_create(void);
int player_is_active(void);
void player_on_tap(int x, int y);
void player_tick(uint64_t now); /* 播放进度/歌词滚动 */
void player_play_song(int idx); /* 从歌单第 idx 首开始播放 */
void player_pause_toggle(int on);
void player_next(int dir); /* dir=+1 下一首 / -1 上一首 */
void player_seek(int64_t ms);
void player_on_swipe(int dir, int dist); /* 横屏切换/返回 */
int player_set_screen(int landscape); /* 1=横屏 0=竖屏;返回之前 */
int player_screen(void);
/* settings */
meow_view *settings_view_create(void);
int settings_is_active(void);
int settings_on_tap(int x, int y);
void settings_swipe(int dir, int dist);
void settings_touch_down(int x, int y);
void settings_touch_move(int x, int y);
void settings_open(void); /* 进设置页 */
void settings_reload(void);
/* search */
meow_view *search_view_create(void);
int search_is_active(void);
int search_on_tap(int x, int y);
void search_swipe(int dir, int dist);
void search_touch_down(int x, int y);
void search_touch_move(int x, int y);
void search_open(void); /* 进搜索页 */
void search_submit(const char *kw); /* 用户确认后发起搜索(风控:仅在确认时发) */
void search_tick(uint64_t now); /* UI tick:检查搜索 worker 完成并刷新 */
/* ---- logic ---- */
int appstate_init(void); /* 初始化 ctx/data/cookie/设置;0=成功 */
void appstate_load_settings(void);
void appstate_save_settings(void);
void appstate_set_logged_in(const char *uid, const char *nickname);
void appstate_logout(void);
int appstate_ensure_uid(void); /* 若 uid 缺失则 login_status_check 取回;0=成功 */
int appstate_load_playlists(void); /* 拉我的歌单 */
int appstate_load_playlist_songs(int idx); /* 兼容:载索引 + 首个窗口(内部走 ensure) */
int appstate_load_playlist_songs_page(int idx, int offset, int limit);
/* 兼容入口(滑动窗口语义,REPORT §17):
* offset=0 重头(detail 全量 trackIds 重建索引、清窗口、
* 物化 [0,limit));offset>0 = 物化 [offset,offset+limit)
* (滑窗+盘缓存+song_detail 补载,内部 ensure_range)。 */
int appstate_song_ensure_range(int lo, int hi);
/* 滑动窗口核心:确保全局下标 [lo,hi) 已物化。
* 窗口未覆盖先滑窗(重叠区保留,出窗槽位失效);
* 缺失槽位先读盘缓存(<cache_dir>/meta/<id>.bin),
* miss 攒批 ≤NCM_SONG_BATCH 走 song_detail,成功落盘。
* worker 线程调用(含网络);返回 NCM_OK/负错误码。 */
int appstate_range_loaded(int lo, int hi); /* 1=[lo,hi) 全部在窗内且有效(UI 判是否需 ensure) */
int appstate_song_copy(int gidx, ncm_song_t *out); /* 锁内读窗:0=已物化拷出;-1=窗外/未物化 */
int appstate_song_fetch(int gidx, ncm_song_t *out); /* 任意全局下标物化(窗→盘→song_detail 单曲),
* 不滑窗(播放线程用,避免与浏览视口抢窗口) */
void appstate_adopt_songs(int n); /* 收编外部已填的 songs[0..n)(搜索结果)为窗口 base=0,
* 同步 song_ids/track_ids_count(播放上下文) */
void appstate_apply_playlist_cover(int idx);
void appstate_set_playing_song(int idx, const char *cover_url);
int appstate_current_playing(void); /* 返回当前播放歌曲下标(UI) */
void autoplay_maybe(void); /* 真机联调 hook(/tmp/ncm_autoplay 触发,自动播已缓存曲目,一次性) */
/* lyric */
int lyric_parse(const char *lrc, const char *tlrc); /* 填充 g_lyric_* */
int lyric_line_at(int64_t ms); /* 当前行 */
const char *lyric_line_text(int i);
const char *lyric_line_tl(int i);
int lyric_count(void);
void lyric_clear(void);
int lyric_state(void); /* 0=空闲/未拉 1=加载中 2=已拉完(可能 0 行=无词) */
/* cover */
void cover_request(const char *url); /* 提交目标封面 URL(后台线程下载+解码) */
meow_img *cover_get(void); /* 当前封面(**持锁返回**;NULL=无;用后必须 cover_put) */
void cover_put(void); /* 配对 cover_get 释放锁(§18 修绘制期 UAF) */
int cover_changed(void); /* 1=有新封面待绘制(UI tick 判变) */
void cover_ack(void); /* UI 消费一次(已见当前版本) */
const char *cover_current_path(void); /* 封面原始文件路径(供 launcher 媒体卡片读);NULL=无 */
void cover_cache_reset(void);
/* ---- play(app/play/)---- */
/* audio_out(后端:默认 SINK_AUDIOD=meow_audiod 独占服务;env NCM_AUDIO_SINK=file/null/alsa
* 或 MEOW_AUDIO_DIRECT=1 选调试后端,见 audio_out.c 文件头) */
int audio_out_open(int sample_rate, int channels, int bits); /* 0=成功;bits∈{16,32}(§19 flac>16bit→32) */
int audio_out_write(const void *samples, int frames); /* <0=错误/结束;样本位宽=open 的 bits */
int audio_out_pause(int paused); /* 1=暂停(停 PCM 时钟不丢流) 0=恢复(流死自动重开);非 AUDIOD 后端恒 0 */
void audio_out_close(void);
void audio_out_set_target(int ms); /* 预留:缓冲目标 */
int audio_out_buffered_ms(void);
/* player_manager:播放线程(下载+解码+输出)驱动 */
int player_manager_start(void); /* 起播放线程;0=成功 */
void player_manager_stop(void); /* 停播(清曲目/置 STOPPED) */
void player_manager_stop_thread(void);/* 停线程(app 退出时 join) */
void player_manager_play(int song_idx); /* 加载并播放 */
void player_manager_pause(int on); /* on=1 暂停 */
void player_manager_next(int dir); /* dir=+1/-1 */
void player_manager_seek(int64_t ms);
void player_manager_set_quality(int level, int cache_gb);
int player_manager_state(void); /* NCM_PC_* */
int player_manager_playing_song(void);
int64_t player_manager_pos_ms(void);
int64_t player_manager_dur_ms(void);
int player_manager_load_lyric(void); /* 为当前曲目拉歌词并解析 */
int player_manager_error(void);
int player_manager_current_song(ncm_song_t *out); /* 播放曲快照锁内拷贝;0=有效 -1=无(REPORT §17) */
void player_manager_dl(int64_t *done, int64_t *total); /* 本曲下载进度(字节;total=0 未知,§18) */
/* songmeta(app/logic/songmeta.c):歌曲元数据本地缓存(<cache_dir>/meta/<id>.bin),
* 拉过的 song_detail 结果序列化落盘,再次入窗免重复请求(REPORT §17)。 */
int songmeta_load(const char *cache_dir, const char *song_id, ncm_song_t *out); /* NCM_OK / NCM_ERR_NOENT */
int songmeta_save(const char *cache_dir, const char *song_id, const ncm_song_t *s);
/* ipc_media:媒体会话上报/收指令 */
int ipc_media_start(void); /* 起 IPC 线程;0=成功 */
void ipc_media_stop(void);
/* ---- 宿主测试面(app/test/test_ncm_app.c 驱动真实页面)---- */
void app_host_set_mock(int mode); /* 0=无 1=home 2=playlist 3=player 4=settings 5=login */
void app_host_paint_page(int page); /* 构建指定页并整帧绘制到 back_buf(供 dump)*/
#endif /* NCM_APP_INTERNAL_H */