Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/logic/lyric.c
T
MeowOS ee104f1b18 feat(ncm): 三期第一批 — 歌单真分页(取消256截断)+专辑封面ncm_cache+歌词后台加载补锁
[agent-239] 仅改 patch_v2/ncm_app/。

1. 歌单分页(REPORT §14.1): track/all 全 404(§13.1) 弃用, 回 detail(n=1)全量 trackIds
   + song_detail 分批(≤NCM_SONG_BATCH=100) 触底逐页加载。appstate_load_playlist_songs_page
   offset=0 重建 song_ids[]/track_ids_count 载第0页, offset>0 追加(失败保留已载);
   playlist.c g_pl_more=(song_count<track_ids_count && <NCM_SONG_MAX), 触底 g_ploffset=song_count
   起下一页(保留滚动), 首屏失败整页空态+点按重试, 追加失败 snack+保留已载。NCM_SONG_MAX
   256→1000(单页渲染上限, bss +~1MB), NCM_SONG_BATCH 64→100, 删 NCM_PLAYLIST_PAGE(track/all 遗留)。

2. 专辑封面(§14.2): cover.c 封面改走 ncm_cache(LRU, 键=djb2(url)→song_cov<h>.jpg 同池),
   缓存命中免下载/未命中下载→open/write/commit 落盘, 仍写 cover_current.jpg 供 launcher;
   顺带修真 bug: save_cover_raw 在已持 g_cm 临界区内再 lock(非递归死锁)→封面永不刷新, 改调用方持锁。
   歌单列表/详情头块封面保持现状。

3. 歌词后台加载(§14.3): lyric_worker 已异步(§11)未改线程模型; 补 lyric.c g_lyric_mu 数据竞争锁,
   写侧 lyric_parse 整段持锁(清空用 lyric_clear_nolock 免二次取锁), 读侧 lyric_count/line_at/
   line_text/line_tl 逐次取锁, 消除 UI/IPC 与 worker 间数据竞争(原 §10.9 FM4 良性竞态)。

验证(0 真实 NCM 请求): bench/pagination_sim.c 离线 stub 驱动真实 appstate 分页函数全 PASS
(130首读全程/5191首封顶1000/空/越界无操作); mipsel build_ncm.sh 0 warning(text 842820/bss 2052068);
宿主 build_ncm_host.sh+MEOW_UI_DUMP home/playlist/player/lands 渲染正常无崩溃; host SDK_SRC 补
meow_pinyin(拼音IME py_predict 漏编致链接断)。ncm md5 b34bc1ed, ncm_host md5 1a2327e0。
未动 sdk/(主会话改 meow_ui_ime.c)/meow_launcherd/meow_amsd/vendor/core; 未 commit 它们。
2026-08-31 19:33:10 +08:00

191 lines
6.4 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.
/* logic/lyric.c — LRC 时间轴解析 + 当前行定位。
*
* 输入:ncm_api_lyric 返回的 lrc(原文)与 tlrc(翻译)。两者均为标准 [mm:ss.xx] 行。
* 解析:把所有带时间戳的行按时间升序排序,存 timestamp(ms)/text;翻译按相同时间戳合并到
* 对应原文行(存 tl)。无匹配的翻译行忽略。流式、有界:NCM_LYRIC_LINES 行,超限截断。
* 纯 C 无 UI,仅依赖标准库。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 歌词数据由 lyric_worker(player_manager.c,后台线程)写(lyric_parse/lyric_clear)而 UI 线程
* 与 IPC 线程读(lyric_count/lyric_line_at/lyric_line_text/lyric_line_tl)。三期第一批(REPORT §14.3)
* 补一把锁消除**数据竞争**(早前为良性竞态 §10.9 FM4;本节要求禁止数据竞争)。锁粒度=函数级,
* 写侧在 lyric_parse 整段持锁、读侧每次访问持锁,无嵌套(清空用 nolock 版,避免二次取锁自锁死)。 */
#define LRC_MAX_TEXT 192
static long s_t[NCM_LYRIC_LINES];
static char s_txt[NCM_LYRIC_LINES][LRC_MAX_TEXT];
static char s_tl[NCM_LYRIC_LINES][LRC_MAX_TEXT];
static int s_n;
static pthread_mutex_t g_lyric_mu = PTHREAD_MUTEX_INITIALIZER;
static void lyric_clear_nolock(void)
{
s_n = 0;
memset(s_t, 0, sizeof(s_t));
memset(s_txt, 0, sizeof(s_txt));
memset(s_tl, 0, sizeof(s_tl));
}
/* 复制一行歌词正文 n 字节到 dst,把「字面反斜杠 n/r」序列(服务器用 \n 把多行文本塞进一行,
* 字面 \n 渲染成豆腐块/凌乱)替换为空格;顺带把 \r\n / \n\r 折叠为单空格。有界 ≤max。 */
static void copy_lrc_text(char *dst, const char *src, size_t n, size_t max)
{
size_t oi = 0, si = 0;
while (si < n && oi < max - 1) {
if (src[si] == '\\' && si + 1 < n && (src[si + 1] == 'n' || src[si + 1] == 'r')) {
dst[oi++] = ' ';
si += 2;
/* \r\n / \n\r 成对 → 只留一个空格(跳过紧随的互补转义,防双空格) */
if (si + 1 < n && src[si] == '\\' &&
((src[si + 1] == 'r' && src[si - 1] == 'n') ||
(src[si + 1] == 'n' && src[si - 1] == 'r')))
si += 2;
} else {
dst[oi++] = src[si++];
}
}
dst[oi] = 0;
}
static void lrc_parse(const char *lrc, int is_trans)
{
if (!lrc) return;
const char *p = lrc;
while (*p) {
/* 找行首 '[' */
while (*p && *p != '[') p++;
if (!*p) break;
/* 解析一个或多个时间标签 */
int have = 0;
long first_ms = 0;
while (*p == '[') {
int m = 0, s = 0, cs = 0;
if (sscanf(p, "[%d:%d.%d]", &m, &s, &cs) >= 2) {
long ms = (long)m * 60000L + (long)s * 1000L + (long)cs * 10L;
if (!have) { first_ms = ms; have = 1; }
/* 跳到本标签结尾 */
const char *q = strchr(p, ']');
if (!q) break;
p = q + 1;
} else {
/* 非时间标签([ti:..] 等)——跳过到行尾 */
const char *q = strchr(p, ']');
if (!q) return;
p = q + 1;
/* 若行内还有时间标签继续解析(极少),否则整行的正文以第一个文本为准 */
break;
}
}
if (have) {
/* 正文 = 到行尾(去 \r\n) */
const char *e = p;
while (*e && *e != '\n' && *e != '\r') e++;
size_t len = (size_t)(e - p);
if (len > LRC_MAX_TEXT - 1) len = LRC_MAX_TEXT - 1;
if (is_trans) {
/* 翻译行:按 first_ms 匹配到已有原文行 */
for (int i = 0; i < s_n; i++) {
if (s_t[i] == first_ms) {
copy_lrc_text(s_tl[i], p, len, LRC_MAX_TEXT);
break;
}
}
} else {
if (s_n < NCM_LYRIC_LINES) {
s_t[s_n] = first_ms;
copy_lrc_text(s_txt[s_n], p, len, LRC_MAX_TEXT);
s_tl[s_n][0] = 0;
s_n++;
}
}
}
while (*p && *p != '\n') p++; /* 跳到下一行 */
if (*p == '\n') p++;
}
}
static int cmp_lines(const void *a, const void *b)
{
const int *ia = (const int *)a, *ib = (const int *)b;
if (s_t[*ia] < s_t[*ib]) return -1;
if (s_t[*ia] > s_t[*ib]) return 1;
return 0;
}
int lyric_parse(const char *lrc, const char *tlrc)
{
pthread_mutex_lock(&g_lyric_mu);
lyric_clear_nolock();
int *idx = NULL;
int result = 0;
lrc_parse(lrc, 0);
lrc_parse(tlrc, 1);
/* 按时间排序(用索引数组) */
idx = (int *)malloc(sizeof(int) * (size_t)(s_n ? s_n : 1));
if (idx) {
for (int i = 0; i < s_n; i++) idx[i] = i;
qsort(idx, (size_t)s_n, sizeof(int), cmp_lines);
long ttmp[NCM_LYRIC_LINES];
char txt[NCM_LYRIC_LINES][LRC_MAX_TEXT];
char tl[NCM_LYRIC_LINES][LRC_MAX_TEXT];
memcpy(ttmp, s_t, sizeof(s_t));
memcpy(txt, s_txt, sizeof(s_txt));
memcpy(tl, s_tl, sizeof(s_tl));
for (int i = 0; i < s_n; i++) {
s_t[i] = ttmp[idx[i]];
memcpy(s_txt[i], txt[idx[i]], LRC_MAX_TEXT);
memcpy(s_tl[i], tl[idx[i]], LRC_MAX_TEXT);
}
free(idx);
}
result = s_n;
pthread_mutex_unlock(&g_lyric_mu);
return result;
}
int lyric_line_at(int64_t ms)
{
pthread_mutex_lock(&g_lyric_mu);
int line = 0;
for (int i = 0; i < s_n; i++) {
if (s_t[i] <= ms) line = i;
else break;
}
pthread_mutex_unlock(&g_lyric_mu);
return line;
}
const char *lyric_line_text(int i)
{
pthread_mutex_lock(&g_lyric_mu);
const char *p = (i < 0 || i >= s_n) ? "" : s_txt[i];
pthread_mutex_unlock(&g_lyric_mu);
return p;
}
const char *lyric_line_tl(int i)
{
pthread_mutex_lock(&g_lyric_mu);
const char *p = (i < 0 || i >= s_n) ? "" : s_tl[i];
pthread_mutex_unlock(&g_lyric_mu);
return p;
}
int lyric_count(void)
{
pthread_mutex_lock(&g_lyric_mu);
int n = s_n;
pthread_mutex_unlock(&g_lyric_mu);
return n;
}
void lyric_clear(void)
{
pthread_mutex_lock(&g_lyric_mu);
lyric_clear_nolock();
pthread_mutex_unlock(&g_lyric_mu);
}