feat(media): 批3歌词数据源框架 — meow_media_session扩实时歌词(lrc/tlrc/seq)+MSG_META_LYRIC上报分片+ncm上报+launcher读meow_media_lyric_* 接批1/2;宿主L1-L7全过/ncm热更27554dd8,launcher待固件收编
This commit is contained in:
1 parent
7ca3e5bd84
commit
2e5b814059
8 files changed
+363
-4
No files matched your search
@@ -81,6 +81,7 @@ enum {
|
||||
MSG_PICKER_RESULT = 0x0071, /* ↑ picker 返回值(候选结果回 A;→launcher→amsd) */
|
||||
MSG_META = 0x0072, /* ↑ 媒体会话上报元数据/进度/状态(阶段 D 附录 B) */
|
||||
MSG_PLAYCTL = 0x0073, /* ↓ 播放控制下发/↑ 控制器转发到最近活跃媒体会话 */
|
||||
MSG_META_LYRIC = 0x0074, /* ↑ 媒体会话上报当前曲歌词 lrc/tlrc(阶段 D · 批3,分片) */
|
||||
/* ---- amsd 控制(AMSD_DESIGN §4.2):amsd→launcher,申请拉起/终止某 App ----
|
||||
* 语义:amsd 为「启动/终止」决策者 → launcher(执行器)执行。payload 见下。 */
|
||||
MSG_AMS_START = 0x00A0, /* → 请求 launcher 拉起某 App(pkg + 可选 activity) */
|
||||
@@ -274,6 +275,29 @@ typedef struct m_playctl {
|
||||
int64_t seek_ms; /* cmd=5: 目标位置 ms;否则 0 */
|
||||
} m_playctl_t; /* 4 (+4 pad) + 8 = 16 B(mipsel int64 8 对齐) */
|
||||
|
||||
/* ---- 媒体会话歌词上报(阶段 D · 批3:live lyric 数据源框架)----
|
||||
* App 播放时把当前曲歌词(lrc 原文 + tlrc 翻译)上报到 meow_media 会话,供桌面歌词页/播放页
|
||||
* 随播放位置实时滚动。约束:launcher 环缓冲 `payload[MEOW_IPC_CARGO(1024)]` 为单帧入队上限,
|
||||
* 故歌词按分片传输(每片 ≤ MEOW_LYRIC_CHUNK 字节)。
|
||||
* - seq = 歌曲代际(换歌/换词自增;launcher 见新 seq 清旧词缓存,同曲分片共用)
|
||||
* - kind = 0 原文 lrc / 1 翻译 tlrc
|
||||
* - total= 该 kind 总字节数(不含 NUL;0=清空该 kind)
|
||||
* - off/len = 本片起始偏移 / 有效字节(首片 off=0;分片按序发送)
|
||||
* 边界:lrc 上限 MEOW_MEDIA_LRC_MAX / tlrc 上限 MEOW_MEDIA_TLRC_MAX(会话内每 App 一份缓冲,
|
||||
* 超限截断)。payload 定长且 == MEOW_IPC_CARGO(即 sizeof(m_lyric_t)=1024)。 */
|
||||
#define MEOW_MEDIA_LRC_MAX 3584u /* 会话内歌词原文缓冲上限 */
|
||||
#define MEOW_MEDIA_TLRC_MAX 1568u /* 会话内歌词翻译缓冲上限 */
|
||||
#define MEOW_LYRIC_CHUNK (MEOW_IPC_CARGO - 44u) /* 单分片数据上限(44=头:32+4+2+2+2+2) */
|
||||
typedef struct m_lyric {
|
||||
char app_id[MEOW_APP_ID_MAX]; /* 32: 上报源头(与 HELLO/META 的 app_id 一致) */
|
||||
uint32_t seq; /* 歌曲代际(新歌/新词自增;分片同 seq) */
|
||||
uint16_t kind; /* 0=原文 lrc;1=翻译 tlrc */
|
||||
uint16_t total; /* 该 kind 总字节数(不含 NUL;0=清空该 kind) */
|
||||
uint16_t off; /* 本片起始偏移(≤ total;首片=0) */
|
||||
uint16_t len; /* 本片有效字节(≤ MEOW_LYRIC_CHUNK) */
|
||||
char data[MEOW_LYRIC_CHUNK]; /* 本片内容(len 后 NUL 结尾,接收侧再截断) */
|
||||
} m_lyric_t; /* 44 + 980 = 1024 B = MEOW_IPC_CARGO */
|
||||
|
||||
/* MSG_NOTIFY ↑:推送/更新通知(freedesktop 对齐 + MeowOS 扩展;定长 LE)。
|
||||
* - summary/body/app_icon/expire_timeout 直通 freedesktop 字段;app_icon 即 SDK `icon`
|
||||
* (icon name 或 path;映射为空串时 launcher 用该 app 默认图标回退);
|
||||
|
||||
@@ -197,13 +197,74 @@ uint32_t meow_media_display_pos(const meow_media_session_t *s)
|
||||
return p;
|
||||
}
|
||||
|
||||
/* 把一帧歌词分片写入会话缓冲(kind=0 lrc / kind=1 tlrc)。含分片边界截断(不越界)。
|
||||
* 返回 0=写入;-1=参数非法。 */
|
||||
static int lyric_put(meow_media_session_t *s, const m_lyric_t *l)
|
||||
{
|
||||
if (!s || !l) return -1;
|
||||
char *buf;
|
||||
uint32_t cap;
|
||||
uint32_t *lenp;
|
||||
if (l->kind == 0) { buf = s->lyric_lrc; cap = MEOW_MEDIA_LRC_MAX; lenp = &s->lyric_lrc_len; }
|
||||
else if (l->kind == 1) { buf = s->lyric_tlrc; cap = MEOW_MEDIA_TLRC_MAX; lenp = &s->lyric_tlrc_len; }
|
||||
else return -1;
|
||||
uint32_t off = l->off, n = l->len;
|
||||
if (off >= cap) { off = cap; n = 0; } /* 起始越界:整片丢弃 */
|
||||
if (n > cap - off) n = cap - off; /* 末段越界:截断到缓冲上限 */
|
||||
if (n) memcpy(buf + off, l->data, n);
|
||||
buf[off + n] = 0; /* 恒 NUL 结尾(供字符串读取) */
|
||||
uint32_t end = off + n;
|
||||
if (end > *lenp) *lenp = end;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_media_lyric(const m_lyric_t *l)
|
||||
{
|
||||
if (!l || !l->app_id[0]) return -1;
|
||||
meow_media_session_t *s = sess_find(l->app_id);
|
||||
if (!s) {
|
||||
/* 会话须先经 META 注册(否则 fd=0,PLAYCTL 会误发到 0 号描述符);丢弃。 */
|
||||
media_log("media: lyric drop app=%s (no session; report META first)", l->app_id);
|
||||
return -1;
|
||||
}
|
||||
/* 新代际(换歌/换词):清旧词缓冲,重开分片写入。 */
|
||||
if (l->seq != s->lyric_seq) {
|
||||
s->lyric_seq = l->seq;
|
||||
s->lyric_lrc_len = 0;
|
||||
s->lyric_tlrc_len = 0;
|
||||
s->lyric_lrc[0] = 0;
|
||||
s->lyric_tlrc[0] = 0;
|
||||
}
|
||||
lyric_put(s, l);
|
||||
media_log("media: LYRIC app=%s seq=%u kind=%u total=%u off=%u len=%u lrc=%u tlrc=%u",
|
||||
s->app_id, l->seq, l->kind, l->total, l->off, l->len,
|
||||
s->lyric_lrc_len, s->lyric_tlrc_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_media_lyric_valid(const meow_media_session_t *s)
|
||||
{
|
||||
return (s && s->used && s->lyric_lrc_len > 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
const char *meow_media_lyric_lrc(const meow_media_session_t *s)
|
||||
{
|
||||
return (s && s->used) ? s->lyric_lrc : "";
|
||||
}
|
||||
|
||||
const char *meow_media_lyric_tlrc(const meow_media_session_t *s)
|
||||
{
|
||||
return (s && s->used) ? s->lyric_tlrc : "";
|
||||
}
|
||||
|
||||
void meow_media_dump(const char *prefix)
|
||||
{
|
||||
for (int i = 0; i < MEOW_MEDIA_MAX; i++) {
|
||||
if (!g_sess[i].used) continue;
|
||||
media_log("%s sess%d app=%s state=%u fd=%d last=%llu pos=%u/%u title=\"%s\"",
|
||||
media_log("%s sess%d app=%s state=%u fd=%d last=%llu pos=%u/%u title=\"%s\" lyric=%u",
|
||||
prefix ? prefix : "media", i, g_sess[i].app_id, g_sess[i].state,
|
||||
g_sess[i].fd, (unsigned long long)g_sess[i].last_active_ms,
|
||||
g_sess[i].pos_ms, g_sess[i].dur_ms, g_sess[i].title);
|
||||
g_sess[i].pos_ms, g_sess[i].dur_ms, g_sess[i].title,
|
||||
(unsigned)(g_sess[i].lyric_lrc_len > 0 ? 1 : 0));
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,15 @@ typedef struct meow_media_session {
|
||||
char artist[128];
|
||||
char album[128];
|
||||
char artwork[192];
|
||||
char lyric[192];
|
||||
char lyric[192]; /* 歌词源路径/槽位(沿用 META.lyric) */
|
||||
/* 批3:当前曲 live 歌词(App 经 MSG_META_LYRIC 上报;有界缓冲,新 seq 重置)。
|
||||
* lyric_lrc/lyric_tlrc 为当前曲歌词原文/翻译文本(桌面歌词页 meow_lyric_parse 用);
|
||||
* lyric_seq=歌曲代际(App 新歌/新词自增,launcher 见新 seq 清旧词)。 */
|
||||
uint32_t lyric_seq;
|
||||
uint32_t lyric_lrc_len;
|
||||
uint32_t lyric_tlrc_len;
|
||||
char lyric_lrc[MEOW_MEDIA_LRC_MAX];
|
||||
char lyric_tlrc[MEOW_MEDIA_TLRC_MAX];
|
||||
} meow_media_session_t;
|
||||
|
||||
/* 依赖注入:send=发一帧(launcher 用 ipc_send 包装;宿主单测用 socket 写);
|
||||
@@ -91,6 +99,17 @@ void meow_media_tick(void);
|
||||
/* 显示用虚拟位置:播放中按墙钟推进 pos_ms(clamp 到 dur);暂停/缓冲/停止原样。 */
|
||||
uint32_t meow_media_display_pos(const meow_media_session_t *s);
|
||||
|
||||
/* ---- 歌词(批3 · live lyric 数据源)----
|
||||
* App 上报当前曲歌词(MSG_META_LYRIC 分片):按 app_id 归并到会话,seq 为歌曲代际、
|
||||
* 新代际清旧词;分片按 kind(0=原文/1=翻译)/off 写入会话内有序缓冲(超 MEOW_MEDIA_LRC_MAX/
|
||||
* MEOW_MEDIA_TLRC_MAX 截断)。会话不存在(未先报 META 注册)→ 丢弃返回 -1(防 fd=0 槽)。
|
||||
* meow_media_lyric_valid/lrc/tlrc:桌面歌词页从当前会话取歌词文本,交由批1 meow_lyric_parse
|
||||
* 解析 + meow_lyric_line_at(meow_media_display_pos(s)) 驱动批2 meow_lyric_view 实时滚动。 */
|
||||
int meow_media_lyric(const m_lyric_t *l); /* 0=已写入;-1=会话不存在/参数非法 */
|
||||
int meow_media_lyric_valid(const meow_media_session_t *s); /* 会话携带有效歌词(lrc 非空) */
|
||||
const char *meow_media_lyric_lrc(const meow_media_session_t *s); /* 原文;无则 "" */
|
||||
const char *meow_media_lyric_tlrc(const meow_media_session_t *s); /* 翻译;无则 "" */
|
||||
|
||||
/* 调试打印(经 ops->log,前缀+每会话一行)。 */
|
||||
void meow_media_dump(const char *prefix);
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/* test_media_lyric.c — 宿主单测:批3 live 歌词数据源框架(meow_media 会话携带 lrc/tlrc → meow_lyric 解析 → 随播放位置命中当前行)。
|
||||
*
|
||||
* 链路:App 上报歌词(分片 m_lyric → meow_media_lyric)→ 会话累积 lrc/tlrc(有界、seq 代际)
|
||||
* → launcher 读取(meow_media_lyric_valid/lrc/tlrc)→ meow_lyric_parse → meow_lyric_line_at(meow_media_display_pos)。
|
||||
*
|
||||
* 覆盖矩阵:
|
||||
* L1 App 上报 lrc/tlrc → 会话携带歌词(valid/lrc/tlrc 正确)
|
||||
* L2 launcher 读取 → meow_lyric_parse 成功(count/行文本/翻译合并正确)
|
||||
* L3 播放位置推进(display_pos 随 PLAYING 单调增)→ line_at 命中当前行(pos 增大 → 行号不降)
|
||||
* L4 分片重组:>MEOW_LYRIC_CHUNK 的 lrc 拆多帧上报,会话按 off 累积成完整串
|
||||
* L5 seq 代际切换(换歌)→ 清旧词;同 seq 重报(幂等)不破坏
|
||||
* L6 会话不存在 → meow_media_lyric 返回 -1;翻译无匹配自动忽略
|
||||
* L7 多 App 独立:另一源上报自己的歌词,互不串
|
||||
*
|
||||
* 编译(宿主):meow_media.c(纯 libc)+ meow_lyric.c(纯 libc+pthread)。
|
||||
* gcc -Wall -Wextra -pthread -o test_media_lyric test_media_lyric.c meow_media.c ../sdk/meow_lyric.c -I. -I../sdk
|
||||
* ./test_media_lyric
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include "meow_media.h"
|
||||
#include "meow_ipc.h"
|
||||
#include "meow_lyric.h"
|
||||
|
||||
static int g_fails = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (cond) printf("PASS: %s\n", msg); \
|
||||
else { printf("FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fails++; } } while (0)
|
||||
|
||||
static size_t test_send(int fd, uint16_t type, uint32_t req_id,
|
||||
const void *payload, uint32_t len)
|
||||
{
|
||||
/* 用不到(本链路无下行),占位满足 meow_media_ops.send 签名。 */
|
||||
(void)fd; (void)type; (void)req_id; (void)payload; (void)len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fill_meta(m_meta_t *m, const char *app, uint32_t state, uint32_t pos, uint32_t dur)
|
||||
{
|
||||
memset(m, 0, sizeof *m);
|
||||
snprintf(m->app_id, sizeof m->app_id, "%s", app);
|
||||
m->state = state; m->pos_ms = pos; m->dur_ms = dur;
|
||||
snprintf(m->title, sizeof m->title, "Song");
|
||||
snprintf(m->artist, sizeof m->artist, "Artist");
|
||||
}
|
||||
|
||||
/* 模拟 ncm 的 send_lyric_kind:把 text 按 ≤MEOW_LYRIC_CHUNK 分片,逐片调 meow_media_lyric。 */
|
||||
static void report_lyric_chunked(const char *app, uint32_t seq, uint16_t kind,
|
||||
const char *text, size_t cmax)
|
||||
{
|
||||
if (!text) text = "";
|
||||
size_t total = strlen(text);
|
||||
if (total > cmax) total = cmax;
|
||||
size_t off = 0;
|
||||
do {
|
||||
size_t n = total - off;
|
||||
if (n > MEOW_LYRIC_CHUNK) n = MEOW_LYRIC_CHUNK;
|
||||
m_lyric_t ly;
|
||||
memset(&ly, 0, sizeof ly);
|
||||
snprintf(ly.app_id, sizeof ly.app_id, "%s", app);
|
||||
ly.seq = seq;
|
||||
ly.kind = kind;
|
||||
ly.total = (uint16_t)total;
|
||||
ly.off = (uint16_t)off;
|
||||
ly.len = (uint16_t)n;
|
||||
if (n) memcpy(ly.data, text + off, n);
|
||||
ly.data[n] = 0;
|
||||
int rc = meow_media_lyric(&ly);
|
||||
if (rc != 0) { printf(" (report chunk off=%zu rc=%d)\n", off, rc); }
|
||||
off += n;
|
||||
} while (off < total);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_media_ops ops;
|
||||
ops.send = test_send;
|
||||
ops.log = NULL;
|
||||
meow_media_init(&ops);
|
||||
|
||||
const char *lrc = "[00:01.00]Hello\n[00:02.00]World\n[00:03.00]Bye\n";
|
||||
const char *tlrc = "[00:01.00]你好\n[00:03.00]再见\n[99:99.99]ignore me\n";
|
||||
|
||||
/* ---- L1+L4 大片(跨分片)---- */
|
||||
char big[4096];
|
||||
int bi = 0;
|
||||
bi += snprintf(big + bi, sizeof big - bi, "[00:00.10]p0\n");
|
||||
for (int i = 1; i <= 40; i++) /* 约 40 行 → >MEOW_LYRIC_CHUNK(980B) */
|
||||
bi += snprintf(big + bi, sizeof big - bi, "[00:%02d.%02d]line%d\n", i / 60, i % 60, i);
|
||||
|
||||
/* 注册会话(META 先行,否则 meow_media_lyric 丢弃) */
|
||||
m_meta_t m;
|
||||
fill_meta(&m, "dev.player.ncm", MEOW_MEDIA_PLAYING, 1000, 120000);
|
||||
CHECK(meow_media_meta(&m, -1) == 0, "META registers session");
|
||||
|
||||
/* 上报歌词:seq=1(新代际),lrc 与 tlrc 各分片 */
|
||||
report_lyric_chunked("dev.player.ncm", 1, 0, lrc, MEOW_MEDIA_LRC_MAX);
|
||||
report_lyric_chunked("dev.player.ncm", 1, 1, tlrc, MEOW_MEDIA_TLRC_MAX);
|
||||
|
||||
const meow_media_session_t *s = meow_media_current();
|
||||
CHECK(s && !strcmp(s->app_id, "dev.player.ncm"), "current() == dev.player.ncm");
|
||||
CHECK(meow_media_lyric_valid(s) == 1, "L1: lyric_valid == 1");
|
||||
CHECK(strcmp(meow_media_lyric_lrc(s), lrc) == 0, "L1: lrc matches");
|
||||
CHECK(strcmp(meow_media_lyric_tlrc(s), tlrc) == 0, "L1: tlrc matches");
|
||||
|
||||
/* ---- L2 launcher 读取 → 解析 ---- */
|
||||
int n = meow_lyric_parse(meow_media_lyric_lrc(s), meow_media_lyric_tlrc(s));
|
||||
CHECK(n == 3, "L2: meow_lyric_parse count == 3");
|
||||
CHECK(meow_lyric_count() == 3, "L2: count() == 3");
|
||||
CHECK(strcmp(meow_lyric_line_text(0), "Hello") == 0, "L2: line0 text Hello");
|
||||
CHECK(strcmp(meow_lyric_line_tl(0), "你好") == 0, "L2: line0 tl 你好 (2000ms 合并)");
|
||||
CHECK(strcmp(meow_lyric_line_text(2), "Bye") == 0, "L2: line2 text Bye");
|
||||
CHECK(strcmp(meow_lyric_line_tl(2), "再见") == 0, "L2: line2 tl 再见");
|
||||
CHECK(meow_lyric_count() == 3, "L2: 无匹配翻译行被忽略(仍 3 行)");
|
||||
|
||||
/* ---- L3 播放位置推进 → 命中当前行 ---- */
|
||||
uint32_t pA = meow_media_display_pos(s);
|
||||
int lineA = meow_lyric_line_at(pA);
|
||||
CHECK(lineA == 0, "L3: display_pos ≈ 1000ms → line 0 (Hello)");
|
||||
usleep(600 * 1000); /* 播放中:display_pos 随墙钟推进 */
|
||||
uint32_t pB = meow_media_display_pos(s);
|
||||
int lineB = meow_lyric_line_at(pB);
|
||||
CHECK(pB >= pA, "L3: display_pos advances (playing)");
|
||||
CHECK(lineB >= lineA, "L3: line_at pos 增大 → 行号不降(随播放命中当前行)");
|
||||
/* 再推进到 ≥3000ms 应到末行 */
|
||||
usleep(1500 * 1000);
|
||||
uint32_t pC = meow_media_display_pos(s);
|
||||
CHECK(pC >= 3000u || lineB >= 2, "L3: pos 推进后到位(或已末行)");
|
||||
CHECK(meow_lyric_line_at(pC) >= meow_lyric_line_at(pB), "L3: 行号单调不降");
|
||||
|
||||
/* ---- L4 大片分片重组(big lrc)---- */
|
||||
meow_media_clear(NULL);
|
||||
fill_meta(&m, "dev.player.ncm", MEOW_MEDIA_PLAYING, 0, 120000);
|
||||
meow_media_meta(&m, -1);
|
||||
report_lyric_chunked("dev.player.ncm", 2, 0, big, MEOW_MEDIA_LRC_MAX);
|
||||
s = meow_media_current();
|
||||
CHECK(s && meow_media_lyric_valid(s) == 1, "L4: big lyric_valid == 1");
|
||||
int bn = meow_lyric_parse(meow_media_lyric_lrc(s), NULL);
|
||||
CHECK(bn > 40, "L4: 大片跨分片重组后解析行数 > 40");
|
||||
CHECK(meow_lyric_line_at(0) == 0 && meow_lyric_line_at(10) == 0, "L4: 低 pos 命中第0行");
|
||||
CHECK(meow_lyric_line_at(meow_lyric_line_time(bn - 1)) == bn - 1, "L4: 末行命中");
|
||||
|
||||
/* ---- L5 seq 代际切换(换歌)清旧词 ---- */
|
||||
report_lyric_chunked("dev.player.ncm", 3, 0, "[00:05.00]New song line\n", MEOW_MEDIA_LRC_MAX);
|
||||
s = meow_media_current();
|
||||
CHECK(meow_media_lyric_valid(s) == 1, "L5: 新代际仍 valid");
|
||||
CHECK(strcmp(meow_media_lyric_lrc(s), "[00:05.00]New song line\n") == 0, "L5: 新词覆盖旧词");
|
||||
CHECK(meow_media_lyric_tlrc(s)[0] == 0, "L5: 新代际清空旧翻译");
|
||||
/* seq 不变重报:幂等(不清) */
|
||||
report_lyric_chunked("dev.player.ncm", 3, 1, "[00:05.00]新歌\n", MEOW_MEDIA_TLRC_MAX);
|
||||
s = meow_media_current();
|
||||
CHECK(strcmp(meow_media_lyric_tlrc(s), "[00:05.00]新歌\n") == 0, "L5: 同 seq 补翻译不清");
|
||||
|
||||
/* ---- L6 会话不存在 → -1;空 app_id → -1 ---- */
|
||||
m_lyric_t empty;
|
||||
memset(&empty, 0, sizeof empty);
|
||||
snprintf(empty.app_id, sizeof empty.app_id, "no.session.app");
|
||||
empty.seq = 9; empty.kind = 0; empty.total = 0; empty.off = 0; empty.len = 0;
|
||||
CHECK(meow_media_lyric(&empty) == -1, "L6: 未注册会话 → -1");
|
||||
memset(&empty, 0, sizeof empty);
|
||||
CHECK(meow_media_lyric(&empty) == -1, "L6: 空 app_id → -1");
|
||||
|
||||
/* ---- L7 多 App 独立 ---- */
|
||||
fill_meta(&m, "com.player2", MEOW_MEDIA_PLAYING, 0, 180000);
|
||||
meow_media_meta(&m, -1);
|
||||
meow_media_clear("dev.player.ncm"); /* 只清 ncm */
|
||||
report_lyric_chunked("com.player2", 1, 0, "[00:01.00]Second app\n", MEOW_MEDIA_LRC_MAX);
|
||||
s = meow_media_current();
|
||||
CHECK(s && !strcmp(s->app_id, "com.player2"), "L7: current == com.player2");
|
||||
CHECK(meow_media_lyric_valid(s) == 1, "L7: player2 歌词有效");
|
||||
CHECK(strcmp(meow_media_lyric_lrc(s), "[00:01.00]Second app\n") == 0, "L7: player2 歌词独立");
|
||||
|
||||
/* ---- 空歌词(无词歌曲):上报空串 → valid=0 ---- */
|
||||
report_lyric_chunked("com.player2", 2, 0, "", MEOW_MEDIA_LRC_MAX);
|
||||
report_lyric_chunked("com.player2", 2, 1, "", MEOW_MEDIA_TLRC_MAX);
|
||||
s = meow_media_current();
|
||||
CHECK(meow_media_lyric_valid(s) == 0, "空词上报 → lyric_valid == 0");
|
||||
|
||||
printf("\n%s (%d fails)\n", g_fails ? "FAILED" : "ALL PASS", g_fails);
|
||||
return g_fails ? 1 : 0;
|
||||
}
|
||||
@@ -1633,6 +1633,22 @@ static void ipc_dispatch(struct meow_ipc_msg *m)
|
||||
(long long)c.seek_ms, rc);
|
||||
break;
|
||||
}
|
||||
/* 阶段D·批3:媒体会话歌词上报(App 播放时把 lrc/tlrc 上报到会话)。分片经
|
||||
* meow_media_lyric 归并(有界缓冲+seq 代际);回 ACK。 */
|
||||
case MSG_META_LYRIC: {
|
||||
m_lyric_t l;
|
||||
memset(&l, 0, sizeof l);
|
||||
memcpy(&l, m->payload, m->plen < (uint16_t)sizeof l ? m->plen : (uint16_t)sizeof l);
|
||||
l.app_id[MEOW_APP_ID_MAX - 1] = 0;
|
||||
int rc = meow_media_lyric(&l);
|
||||
m_ack_t a;
|
||||
a.code = MEOW_OK; /* 会话策略:无会话丢弃(rc=-1),不视为错误 */
|
||||
a.status = (uint32_t)rc;
|
||||
ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a);
|
||||
logmsg("ipc: META_LYRIC app=%s kind=%u seq=%u total=%u off=%u len=%u rc=%d -> ACK",
|
||||
l.app_id, l.kind, l.seq, l.total, l.off, l.len, rc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
logmsg("ipc: unsupported frame type 0x%04x (req %u)", m->type, m->req_id);
|
||||
{
|
||||
|
||||
@@ -295,3 +295,47 @@ void ipc_media_stop(void)
|
||||
/* 线程在 200ms poll 内退出(detach,不 join)*/
|
||||
g_thr_on = 0;
|
||||
}
|
||||
|
||||
/* ---- 批3:live 歌词上报(lrc 原文 + tlrc 翻译 → 媒体会话,桌面歌词页实时滚动源)---- */
|
||||
|
||||
/* 歌词代际(换歌/换词自增;launcher 见新 seq 清旧词)。IC 线程外调用(lyric_worker),
|
||||
* 单线程写(worker 是唯一调用者),无需锁。 */
|
||||
static uint32_t g_lyr_media_seq;
|
||||
|
||||
/* 把一个 kind 的歌词文本按分片发到媒体会话(≤MEOW_LYRIC_CHUNK 一帧)。cmax=该 kind 上限:
|
||||
* 0=MEOW_MEDIA_LRC_MAX,1=MEOW_MEDIA_TLRC_MAX。total=0(空串)时发一个空帧=清词。 */
|
||||
static void send_lyric_kind(int fd, uint32_t seq, uint16_t kind, const char *text, size_t cmax)
|
||||
{
|
||||
if (!text) text = "";
|
||||
size_t total = strlen(text);
|
||||
if (total > cmax) total = cmax; /* 接收侧缓冲上限:超限截断 */
|
||||
size_t off = 0;
|
||||
do {
|
||||
size_t n = total - off;
|
||||
if (n > MEOW_LYRIC_CHUNK) n = MEOW_LYRIC_CHUNK;
|
||||
m_lyric_t ly;
|
||||
memset(&ly, 0, sizeof ly);
|
||||
ncm_strlcpy(ly.app_id, "dev.player.ncm", sizeof ly.app_id);
|
||||
ly.seq = seq;
|
||||
ly.kind = kind;
|
||||
ly.total = (uint16_t)total;
|
||||
ly.off = (uint16_t)off;
|
||||
ly.len = (uint16_t)n;
|
||||
if (n) memcpy(ly.data, text + off, n);
|
||||
ly.data[n] = 0;
|
||||
send_frame(fd, MSG_META_LYRIC, seq, &ly, sizeof ly);
|
||||
off += n;
|
||||
} while (off < total);
|
||||
}
|
||||
|
||||
void ipc_media_lyric_report(const char *lrc, const char *tl)
|
||||
{
|
||||
g_lyr_media_seq++;
|
||||
int fd = connect_path(sock_path());
|
||||
if (fd < 0) { meow_log(MEOW_LOG_WARN, "ipc: lyric no launcher socket"); return; }
|
||||
send_lyric_kind(fd, g_lyr_media_seq, 0, lrc, MEOW_MEDIA_LRC_MAX); /* 原文 */
|
||||
send_lyric_kind(fd, g_lyr_media_seq, 1, tl, MEOW_MEDIA_TLRC_MAX); /* 翻译 */
|
||||
close(fd);
|
||||
meow_log(MEOW_LOG_INFO, "ipc: lyric reported seq=%u lrc=%zuB tlrc=%zuB",
|
||||
g_lyr_media_seq, lrc ? strlen(lrc) : 0, tl ? strlen(tl) : 0);
|
||||
}
|
||||
@@ -1045,8 +1045,14 @@ static void *lyric_worker(void *arg)
|
||||
pthread_mutex_lock(&g_lyrmu); g_lyr_state = 0; pthread_mutex_unlock(&g_lyrmu);
|
||||
continue;
|
||||
}
|
||||
lrc[0] = tl[0] = 0; /* 默认空串(fetch 失败/空词时上报清词,防会话留旧曲歌词) */
|
||||
int rc = ncm_api_lyric(g_st.ctx.device_id, g_st.ctx.cookie, id, lrc, 64 * 1024, tl, 32 * 1024);
|
||||
if (rc == NCM_OK) lyric_parse(lrc, tl);
|
||||
if (rc == NCM_OK) {
|
||||
lyric_parse(lrc, tl);
|
||||
ipc_media_lyric_report(lrc, tl); /* 批3:上报 live 歌词到媒体会话 */
|
||||
} else {
|
||||
ipc_media_lyric_report("", ""); /* 无词/拉取失败:清 media session 该曲歌词 */
|
||||
}
|
||||
free(lrc); free(tl);
|
||||
|
||||
pthread_mutex_lock(&g_lyrmu);
|
||||
|
||||
@@ -58,6 +58,8 @@ size_t ncm_strlcpy(char *dst, const char *src, size_t sz)
|
||||
}
|
||||
void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; }
|
||||
void meow_audio_on_preempt(meow_audio_preempt_fn cb, void *ctx) { (void)cb; (void)ctx; }
|
||||
/* core/ncm_common.c 提供 ncm_now_ms(墙钟毫秒);本 sim 不链 ncm_common.o,提供同语义桩。 */
|
||||
int64_t ncm_now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec*1000 + ts.tv_nsec/1000000; }
|
||||
|
||||
/* ---- appstate stub:窗内全命中,id=1000+idx;fee 可注入(§20 P8) ---- */
|
||||
static int g_fee = 0;
|
||||
@@ -315,6 +317,8 @@ void audio_out_set_target(int ms) { (void)ms; }
|
||||
/* ---- 歌词(未被调用的 worker 会被 gc-sections 丢弃;lyric_clear 由 worker 用,防御性提供) ---- */
|
||||
void lyric_clear(void) { }
|
||||
int lyric_parse(const char *a, const char *b) { (void)a; (void)b; return 0; }
|
||||
/* 批3:player_manager 的 lyric_worker 会上报歌词到媒体会话;本 sim 不链 ipc_media.c,提供空桩。 */
|
||||
void ipc_media_lyric_report(const char *lrc, const char *tl) { (void)lrc; (void)tl; }
|
||||
|
||||
static int g_fail;
|
||||
#define CHECK(cond, fmt, ...) do { \
|
||||
|
||||
Reference in new issue
Block a user