feat: M6.10–M6.19 批量收编 + M6.19 推卡验收

- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
AskaEth committed 2026-09-03 12:16:01 +08:00
1 parent c2ee7626a4
commit 3d70a3dd2c
176 files changed
+15507 -597

No files matched your search

+84 -2
View File
@@ -32,6 +32,7 @@
#include <sys/stat.h>
#include "decoder.h"
#include "stream_src.h"
#include "mem_watch.h"
static pthread_t g_thr;
static int g_thr_on;
@@ -43,6 +44,10 @@ static char g_path[512]; /* 本地音频路径(缓存,mp3/flac
static int64_t g_file_bytes;
static int g_level; /* 音质档(来自设置) */
static int g_cache_gb;
/* 任务3(§re-entry):pending_replay=1 表示「切音质/缓存后点了正在播的同曲」——此时重播须换档
* (P12 语义,seq++),不得被「同曲不重播」护栏吞掉。set_quality 置位;player_manager_play 的
* 同曲护栏据它分流:pending=1 走重播(换档),pending=0 只聚焦不重播(点正在播的曲)。 */
static int g_pending_replay;
/* 被抢占回调(SDK 读线程分发;audiod 后开流赢):走 g_play 命令通道暂停(内部持锁),
* 不直接动锁外状态。恢复时 audio_out_pause(0) 会检测流死并自动重开(见 audio_out.c)。 */
@@ -90,12 +95,25 @@ void player_manager_set_quality(int level, int cache_gb)
pthread_mutex_lock(&g_play.mu);
g_play.err = 0;
ncm_cache_set_limit_gb(&g_st.ctx.cache, cache_gb);
g_pending_replay = 1; /* 任务3:改档后点正在播的同曲 → 强制换档重播(§25 P12) */
pthread_mutex_unlock(&g_play.mu);
}
void player_manager_play(int song_idx)
{
pthread_mutex_lock(&g_play.mu);
/* 任务3:点「正在播的曲」(同 idx 且 PLAYING/PAUSED,非切档重播)→ 只聚焦不重播:
* 不 seq++、不置 BUFFER——旧曲继续出声/停原状态,UI 由调用方 nav 到播放页。
* 注意:set_quality 已置 g_pending_replay=1(切音质后重播同曲须换档,P12),此护栏放行。
* 非 PLAYING/PAUSED(STOPPED/另一曲)不受影响,照旧起播。 */
if (song_idx == g_play.cur_song && !g_pending_replay &&
(g_play.state == NCM_PC_PLAYING || g_play.state == NCM_PC_PAUSED)) {
pthread_mutex_unlock(&g_play.mu);
meow_log(MEOW_LOG_INFO, "play: re-enter current idx=%d state=%d -> no restart",
song_idx, g_play.state);
return;
}
g_pending_replay = 0; /* 消费掉(新播放意图落地,后续同曲 tap 恢复「只聚焦」) */
g_play.cur_song = song_idx;
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
@@ -108,6 +126,7 @@ void player_manager_play(int song_idx)
g_play.underruns = 0; /* §21:边下边播统计随新播放意图清零 */
g_play.underrun_ms = 0;
g_play.stalled = 0;
g_play.refaults = 0; /* §2.3:refault 取证随新播放意图清零 */
pthread_mutex_unlock(&g_play.mu);
}
@@ -135,6 +154,7 @@ void player_manager_next(int dir)
int cnt = appstate_queue_count(); /* §23:按队列来源(窗口全量 或 搜索条数) */
if (c >= cnt) c = cnt - 1;
if (c != g_play.cur_song) {
g_pending_replay = 0; /* 新播放意图落地:清「切档重播」待办 */
g_play.cur_song = c;
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
@@ -147,6 +167,7 @@ void player_manager_next(int dir)
g_play.underruns = 0;
g_play.underrun_ms = 0;
g_play.stalled = 0;
g_play.refaults = 0;
}
pthread_mutex_unlock(&g_play.mu);
}
@@ -154,6 +175,7 @@ void player_manager_next(int dir)
void player_manager_stop(void)
{
pthread_mutex_lock(&g_play.mu);
g_pending_replay = 0; /* 停止:清「切档重播」待办 */
g_play.cur_song = -1;
g_play.state = NCM_PC_STOPPED;
g_play.pos_ms = 0;
@@ -189,8 +211,19 @@ typedef struct {
int *st_ptr; /* 指向 dl_thread 的 status 局部变量(同线程,无竞态) */
int need_fresh; /* 416/其它:服务器拒绝续传 → 放 .part、交调用方整首重下 */
uint64_t skip; /* 200 忽略 Range:body 前 skip 字节与 .part 前缀重复,丢弃 */
/* page-cache 治理(A:meow_cachegov)登记路径:播放实际读的文件(流式=.tmp/.part,命中=final)。 */
char gov_path[512];
/* 曲终 L0 用:commit 后的正式路径(流式 commit rename 后 = final;命中=gov_path 同)。 */
char gov_final_path[512];
} song_src_t;
/* page-cache 治理(A):SDK meow_cachegov 的 read_pos 回调——返回当前解码逻辑读指针
* (stream_src 内部维护,经 stream_src_read_pos 读取;回调上下文=stream_src*)。 */
static uint64_t gov_read_pos(void *ctx)
{
return stream_src_read_pos((stream_src_t *)ctx);
}
/* http 体回调(下载线程上下文;ncm_http 每次投递 ≤4KB):write(2) 直写落盘 +
* 锁内推进 written + broadcast 唤醒读端 + 进度反馈。
* 阶段3 续传(§5.2):首 body 字节按 response status 切换写姿态——
@@ -201,6 +234,11 @@ static int stream_dl_cb(void *ctx, const unsigned char *data, size_t len)
{
song_src_t *ss = (song_src_t *)ctx;
if (stream_src_aborted(ss->src)) return 1; /* 切歌/停止:中止 http */
/* page-cache 治理(§2.3/§3):下载 cb 查 meow_cachegov_download_paused()(A 回退硬暂停
* OR svc_perf C Cached 降载)→ 暂停下载(限速),优先解码播放;不打断播放线程
* (读端追上下载走既有 underrun/停滞联动)。每 50ms 查一次中止,切歌可立即脱身。 */
while (meow_cachegov_download_paused() && !stream_src_aborted(ss->src))
usleep(50 * 1000);
if (ss->first_body) {
ss->first_body = 0;
int st = ss->st_ptr ? *ss->st_ptr : 0;
@@ -295,6 +333,15 @@ static void song_src_dispose(song_src_t *ss)
if (ss->src) { stream_src_close(ss->src); ss->src = NULL; }
}
/* page-cache 治理(A)曲终清理:注销 L1 登记条目 + 下载完整(commit 成功)则对正式文件做
* L0 整文件 DONTNEED(仅曲终安全窗口;未 commit/中断保留 .part 不丢)。 */
static void song_gov_cleanup(song_src_t *ss)
{
if (ss->gov_path[0]) meow_cachegov_unregister(ss->gov_path);
if (ss->dl_rc == NCM_OK && ss->gov_final_path[0])
meow_cachegov_drop_finished(ss->gov_final_path);
}
/* 缓存键(用户定案 NCM_CACHE_DESIGN §2.5):歌曲ID + 音质档位 → "song_<id>_<level>.<ext>"。
* 同歌同档命中不重复下载;换档=新条目,旧档留给 LRU 自然淘汰。
* §20:键后缀用 ncm_api_level_key(sky 与 sky_c51 可区分)。 */
@@ -587,6 +634,7 @@ static void run_song(int idx)
g_play.play_song = s;
g_play.play_song_valid = 1;
pthread_mutex_unlock(&g_play.mu);
mem_watch_song_begin(); /* 边播边监控:本曲峰值从当前起算(下载/解码/播放全程记录) */
song_src_t ss;
int64_t file_bytes = 0;
@@ -602,11 +650,22 @@ static void run_song(int idx)
}
if (!still_intent(idx, my_seq)) { /* 预缓冲完成但已切歌/同曲重播:勿开解码/勿动状态 */
song_src_dispose(&ss);
song_gov_cleanup(&ss); /* 未登记(register 在其后)时为 no-op,统一清理 */
mem_watch_song_end(); /* 释放钩子:join 下载线程+关源+缓冲 free+.part 收尾 */
return;
}
ncm_strlcpy(g_path, path, sizeof g_path);
g_file_bytes = file_bytes;
/* page-cache 治理(A):登记本曲缓存文件(播放实际读的路径)+ read_pos 回调,供 L1 丢已读
* 前缀 / refault 回退。cache 命中(ss.w==NULL)path=正式文件;流式 path=.tmp/.part
* (cur file 即正被 pread 的文件,DONTNEED 只作用于它;正式路径待 commit 后 L0 用)。 */
ncm_strlcpy(ss.gov_path, path, sizeof ss.gov_path);
if (ss.w) ncm_strlcpy(ss.gov_final_path, ncm_cache_writer_final_path(ss.w), sizeof ss.gov_final_path);
else ncm_strlcpy(ss.gov_final_path, path, sizeof ss.gov_final_path);
meow_cachegov_register(ss.gov_path, (uint64_t)(file_bytes > 0 ? file_bytes : 0),
gov_read_pos, ss.src);
int64_t dur = s.duration_ms > 0 ? s.duration_ms : 0;
/* 解码分发(§19/§21):stream_src 上 magic 探测 fLaC→dr_flac / 其它→minimp3(open_cb
@@ -616,6 +675,8 @@ static void run_song(int idx)
int64_t total_frames = 0;
if (ncm_dec_open_src(&dec, ss.src, &rate, &ch, &bits, &total_frames) != 0) {
song_src_dispose(&ss);
song_gov_cleanup(&ss);
mem_watch_song_end(); /* 释放钩子 */
if (!still_intent(idx, my_seq)) return; /* open 期间(flac 元数据等待)已切歌/重播 */
set_err(NCM_ERR_IO, "decode open failed"); return;
}
@@ -623,7 +684,8 @@ static void run_song(int idx)
/* 音频输出:按实际流参数开(flac ≤16bit→S16,>16bit→S32;audiod 侧格式归一+原生率跟随) */
if (audio_out_open(rate, ch, bits) != 0) {
ncm_dec_close(dec); song_src_dispose(&ss);
ncm_dec_close(dec); song_src_dispose(&ss); song_gov_cleanup(&ss);
mem_watch_song_end(); /* 释放钩子 */
set_err(NCM_ERR_IO, "audio open failed"); return;
}
@@ -645,6 +707,7 @@ static void run_song(int idx)
int paused_edge = 0; /* 暂停沿:进暂停已向 audiod 发 pause */
int stalled_edge = 0; /* §21 停滞沿:已对 audiod pause(1),待数据恢复 pause(0) */
int underruns_seen = 0;
int refaults_seen = 0; /* §2.3:本曲 DONTNEED 丢页重读 TF 计数(取证) */
for (;;) {
if (g_app_quit) break;
@@ -751,6 +814,18 @@ static void run_song(int idx)
idx, u, (unsigned long long)wm);
}
}
/* page-cache 治理(A):refault 取证落 g_play + 节拍执行 L1 丢弃/refault 回退 */
{
int rf = stream_src_refaults(ss.src);
if (rf != refaults_seen) {
refaults_seen = rf;
pthread_mutex_lock(&g_play.mu);
g_play.refaults = rf;
pthread_mutex_unlock(&g_play.mu);
meow_log(MEOW_LOG_INFO, "play: refault idx=%d count=%d", idx, rf);
}
meow_cachegov_tick();
}
/* 更新进度(~200ms 节流)*/
int64_t pos = rate > 0 ? frames_done * 1000 / rate : 0;
pthread_mutex_lock(&g_play.mu);
@@ -763,6 +838,9 @@ static void run_song(int idx)
ncm_dec_close(dec);
/* 下载完成由播放线程 commit(rename) / 中断 abort(阶段 2 语义,§21) */
song_src_dispose(&ss);
song_gov_cleanup(&ss); /* page-cache 治理(A)曲终:注销 L1 条目 + commit 成功则 L0 整文件 DONTNEED */
mem_watch_song_end(); /* 调度器释放钩子:上一首缓冲完全清空(decoder 关 + 下载线程
* join + 源关 + 缓冲 free + .part 收尾)——给下一首让内存 */
/* 若应继续播下一首且非因命令打断,由外层处理(外层见 cur==idx 且需自然结束) */
}
@@ -835,7 +913,9 @@ static void *lyric_worker(void *arg)
}
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);
lyric_clear();
pthread_mutex_lock(&g_lyrmu);
@@ -898,6 +978,7 @@ int player_manager_start(void)
g_cache_gb = g_st.cache_limit_gb;
pthread_mutex_init(&g_play.mu, NULL);
meow_audio_on_preempt(on_audio_preempt, NULL); /* 被抢占 → 暂停(SDK 读线程分发) */
mem_watch_start(); /* 边播边监控(防 OOM):起监控线程读 RSS,驱下载降载 */
if (ncm_thread_create(&g_thr, player_thread, NULL) != 0) return -1;
g_thr_on = 1;
return 0;
@@ -908,6 +989,7 @@ void player_manager_stop_thread(void)
g_want_run = 0;
g_app_quit = 1;
audio_out_close(); /* 打断阻塞写 */
mem_watch_stop(); /* 停监控线程 */
if (g_thr_on) {
pthread_join(g_thr, NULL);
g_thr_on = 0;