Files

277 lines
9.3 KiB
C
Raw Permalink 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.
/* meow_media.c — MeowOS 媒体会话注册表(阶段 D · 附录 B / D1)。
*
* 实现说明见 meow_media.h。自包含:仅依赖 meow_ipc.h 结构 + <time.h>(monotonic)+
* <string.h>/<stdarg.h>;发送/日志经 meow_media_ops 注入,宿主可单测。
*/
#include "meow_media.h"
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
static const meow_media_ops *g_ops = NULL;
static meow_media_session_t g_sess[MEOW_MEDIA_MAX];
static uint64_t g_seq = 0; /* 全局活跃净序(每次 META +1,严格区分先后) */
/* 单调毫秒(self-contained;launcher/main_menu 的 now_ms 与此同源 CLOCK_MONOTONIC)。 */
static uint64_t media_now_ms(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) ts.tv_sec = ts.tv_nsec = 0;
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)(ts.tv_nsec / 1000000u);
}
static void media_log(const char *fmt, ...)
{
if (!g_ops || !g_ops->log) return;
char buf[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
g_ops->log("%s", buf);
}
static char *media_cp(char *dst, size_t dstsz, const char *src)
{
if (!dst || dstsz == 0) return dst;
if (!src) src = "";
snprintf(dst, dstsz, "%s", src);
dst[dstsz - 1] = 0;
return dst;
}
void meow_media_init(const meow_media_ops *ops)
{
g_ops = ops;
}
static meow_media_session_t *sess_find(const char *app_id)
{
if (!app_id || !app_id[0]) return NULL;
for (int i = 0; i < MEOW_MEDIA_MAX; i++)
if (g_sess[i].used && strcmp(g_sess[i].app_id, app_id) == 0)
return &g_sess[i];
return NULL;
}
/* 找空槽;满时覆盖最旧槽(LRU 式,防单源漂移占满)。 */
static meow_media_session_t *sess_alloc(void)
{
int slot = -1;
uint64_t oldest = UINT64_MAX;
for (int i = 0; i < MEOW_MEDIA_MAX; i++) {
if (!g_sess[i].used) return &g_sess[i];
if (g_sess[i].last_active_ms < oldest) { oldest = g_sess[i].last_active_ms; slot = i; }
}
if (slot >= 0) {
media_log("media: sessions full, evict app=%s", g_sess[slot].app_id);
memset(&g_sess[slot], 0, sizeof g_sess[slot]);
return &g_sess[slot];
}
return NULL;
}
int meow_media_meta(const m_meta_t *m, int fd)
{
if (!m || !m->app_id[0]) return -1;
meow_media_session_t *s = sess_find(m->app_id);
if (!s) {
s = sess_alloc();
if (!s) { media_log("media: meta drop app=%s (no slot)", m->app_id); return -1; }
s->used = 1;
media_cp(s->app_id, sizeof s->app_id, m->app_id);
}
s->fd = fd;
s->last_active_ms = media_now_ms();
s->active_seq = ++g_seq; /* 严格递增:跨会话先后唯一,不受时钟粒度影响 */
s->state = m->state;
s->pos_ms = m->pos_ms;
s->dur_ms = m->dur_ms;
s->flags = m->flags;
media_cp(s->title, sizeof s->title, m->title);
media_cp(s->artist, sizeof s->artist, m->artist);
media_cp(s->album, sizeof s->album, m->album);
media_cp(s->artwork, sizeof s->artwork, m->artwork);
media_cp(s->lyric, sizeof s->lyric, m->lyric);
media_log("media: META app=%s fd=%d state=%u pos=%u/%u title=\"%s\"",
s->app_id, fd, s->state, s->pos_ms, s->dur_ms, s->title);
return 0;
}
const meow_media_session_t *meow_media_current(void)
{
/* 最近活跃 = active_seq 最大者(严格先后;last_active_ms 仅作进度推进用)。 */
const meow_media_session_t *best = NULL;
uint64_t t = 0;
for (int i = 0; i < MEOW_MEDIA_MAX; i++) {
if (g_sess[i].used && g_sess[i].active_seq >= t) {
t = g_sess[i].active_seq;
best = &g_sess[i];
}
}
return best;
}
int meow_media_has_source(void)
{
const meow_media_session_t *s = meow_media_current();
return (s && s->state != MEOW_MEDIA_STOPPED) ? 1 : 0;
}
int meow_media_app_playing(const char *app_id)
{
const meow_media_session_t *s = meow_media_current();
if (!s || !app_id || !app_id[0]) return 0;
return (s->state == MEOW_MEDIA_PLAYING && strcmp(s->app_id, app_id) == 0) ? 1 : 0;
}
int meow_media_app_resident(const char *app_id)
{
const meow_media_session_t *s = sess_find(app_id);
return (s && s->state != MEOW_MEDIA_STOPPED) ? 1 : 0;
}
static int send_playctl(const meow_media_session_t *s, uint32_t cmd, int64_t seek_ms)
{
if (!s || !s->used || !g_ops || !g_ops->send) return -1;
m_playctl_t c;
memset(&c, 0, sizeof c);
c.cmd = cmd;
c.seek_ms = seek_ms;
size_t n = g_ops->send(s->fd, MSG_PLAYCTL, 0, &c, sizeof c);
media_log("media: PLAYCTL -> %s cmd=%u seek=%lld n=%zu",
s->app_id, cmd, (long long)seek_ms, n);
/* ops->send 返回写入总字节(16B 头 + 负载);成功 = 完整帧已写出。 */
return (n >= MEOW_IPC_HDR_LEN + sizeof c) ? 0 : -1;
}
int meow_media_playctl(uint32_t cmd, int64_t seek_ms)
{
return send_playctl(meow_media_current(), cmd, seek_ms);
}
int meow_media_playctl_to(const char *app_id, uint32_t cmd, int64_t seek_ms)
{
meow_media_session_t *s = sess_find(app_id);
return s ? send_playctl(s, cmd, seek_ms) : -1;
}
void meow_media_clear(const char *app_id)
{
int n = 0;
for (int i = 0; i < MEOW_MEDIA_MAX; i++) {
if (g_sess[i].used &&
(!app_id || !app_id[0] || strcmp(g_sess[i].app_id, app_id) == 0)) {
media_log("media: clear app=%s", g_sess[i].app_id);
memset(&g_sess[i], 0, sizeof g_sess[i]);
n++;
}
}
if (app_id && app_id[0]) media_log("media: cleared %d sess for %s", n, app_id);
else if (n) media_log("media: cleared all %d sess", n);
}
void meow_media_tick(void)
{
uint64_t now = media_now_ms();
for (int i = 0; i < MEOW_MEDIA_MAX; i++) {
if (!g_sess[i].used) continue;
if (g_sess[i].state == MEOW_MEDIA_PLAYING || g_sess[i].state == MEOW_MEDIA_BUFFER) {
if (now - g_sess[i].last_active_ms > MEOW_MEDIA_STALE_MS) {
media_log("media: stale source %s (no META %llu ms) -> clear",
g_sess[i].app_id,
(unsigned long long)(now - g_sess[i].last_active_ms));
memset(&g_sess[i], 0, sizeof g_sess[i]);
}
}
}
}
uint32_t meow_media_display_pos(const meow_media_session_t *s)
{
if (!s) return 0;
uint32_t p = s->pos_ms;
if (s->state == MEOW_MEDIA_PLAYING) {
uint64_t now = media_now_ms();
uint64_t el = (now > s->last_active_ms) ? (now - s->last_active_ms) : 0;
uint32_t adv = (el > 0xFFFFFFFFull) ? 0xFFFFFFFFu : (uint32_t)el;
p = s->pos_ms + adv;
if (s->dur_ms && p > s->dur_ms) p = s->dur_ms;
}
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\" 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,
(unsigned)(g_sess[i].lyric_lrc_len > 0 ? 1 : 0));
}
}