Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/logic/lyric.c
T
MeowOS 7bd6962ccb feat(ncm §22): 播放页重做——封面页/歌词页双页(NCM_CACHE_DESIGN §6 定案)
- 去标题栏(不再用 ncm_topbar);返回=左缘手势/系统返回钮语义
- 双页结构:封面页/歌词页,屏幕正中左/右滑切换(与左缘≤24px返回、顶部通知分流),
  页面指示点显示当前页;中部横滑=on_touch_move 锁存 page-drag + on_tap/on_swipe 判向
- 封面页:方形大图+圆角(无旋转动画)+进度条(点 seek)+MD3 矢量控制钮(prev/play/pause/next,
  新增 app/ui/ncm_icons.h 32x32 单通道 alpha,参照 sb_icons.h 范式)+元信息+实际音质档
- 歌词页:LRC 滚动(当前行居中高亮、上下渐隐)+点行跳转(新增 lyric_line_time);
  默认=封面模糊置底(封面铺底+主题 scrim),省电=封面背景+黑色半透明叠加层(设置 ncm.player_bg_mode)
- 日夜间:主题配色只映射到半透明叠加层/scrim/accent(红),不动沉浸底(NCM_PLAYER_BG 恒定)
- 分带刷新沿用:封面页=封面静态带+动态带(互不重叠 bbox,tick 只刷动态带);歌词页换行(~5s)才重画
- 性能:宿主 4 页 PPM 核验+手势自检 PASS;mipsel -Wall -Wextra 0 warning;qemu core 回归 PASS;
  0 真实请求(封面/歌词用本地样本注入)。未部署真机(主会话统一)

md5: ncm 730e0b6e / ncm_host 500ac85 / player.c 00934de / ncm_icons.h 20f0bba
2026-09-01 18:52:14 +08:00

200 lines
6.7 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;
}
/* 第 i 行时间戳(ms)。越界返回 -1。供播放页「点行跳转」(§22:seek 到该行时间)。 */
long lyric_line_time(int i)
{
pthread_mutex_lock(&g_lyric_mu);
long t = (i < 0 || i >= s_n) ? -1 : s_t[i];
pthread_mutex_unlock(&g_lyric_mu);
return t;
}
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);
}