Files
MeowOS-SDK/meow_ui_media.c
T
MeowOS c00872080a fix(media): 物理播放/暂停键(164)改发 PLAYCTL cmd=6 toggle + App 前台状态栏音量%实时刷新
Bug1: SDK meow_ui_media 按 g_last_state 本地判 play/pause,但 ncm 走自有 ipc_media
线程不经 SDK meta 上报 → g_last_state 恒 0 → 恒发 cmd=1(播放),播放中按键无效。
修:SDK 164 恒发 cmd=6 toggle(删 g_last_state 判定),ncm apply_playctl case 6
按 player_manager_state() 当前态翻转(源是状态权威);launcher meow_media/util
本就透传 cmd 不动结构,仅 meow_ipc.h/SPEC_REF 注释补 6=toggle。媒体卡片/NP 页
按钮(按 launcher 侧 s->state 判,来自 META 是准的)维持现状。

Bug2: App 前台时状态栏为 launcher 叠加层,App 内 SDK 改音量/亮度只写
settings.conf+DAC,launcher meow_settings 内存表不感知 → g_sys.vol 停旧值。
修:app_overlay_statusbar 前插 app_overlay_settings_poll——1s 节流 stat
settings.conf,mtime 变才 meow_settings_init 重读+sync 缓存+g_sys.vol/brightness
(meow_settings 新增 meow_settings_file_path() 访问器防硬编码分叉)。

验证:宿主 test_sdk_input_keys 19/19(K4 改 toggle 断言)、test_media +cmd=6
透传用例 ALL PASS、ncm bench/toggle_sim 10/10(新)、player_flow/cache_evict
sim PASS、ncm core 宿主+qemu-mipsel PASS、test_settings_brightness PASS;
mipsel 交叉 -Wall -Wextra 0 warning(改动文件单独验+整构建)。
真机(0.7.91):ncm 热更(.new+mv)后 autoplay(Docu) 起播缓存曲 → meow_ipc
playctl 6 二连发 → paused(pos=139691)/resumed 日志实证 toggle 全链路。
md5:ncm 8f10eee5 / meow_ui.a 7b359ec9(gitignore)/ launcher eba4f2b2
(/usr/bin 只读,fw_content.conf 已更新待固件)。物理键 164 实体按压留用户终验。
2026-09-01 14:44:55 +08:00

314 lines
12 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.
/* meow_ui_media.c — App 侧媒体会话封装(SDK 公共件;见 meow_ui_media.h)。
*
* 传输:AF_UNIX SOCK_STREAM 到 MEOW_IPC_SOCK(缺省 /usr/data/meow_ipc.sock)。后台线程持有
* 持久连接:HELLO(app_id, MF_PLAYBACK) 注册为媒体源 + poll 读 launcher 下发 MSG_PLAYCTL(经
* 该连接 fd 回发,见 launcher meow_media.c send_playctl)。meow_ui_media_meta() 在调用线程
* 直接经同一连接发 MSG_META(全双工:写 META、读 PLAYCTL 互不干扰)。
*
* 连接生命周期(best-effort):懒连(首次 meta / 注册回调时 connect+HELLO+ACK);
* 断线(读失败/写失败)→ close+置 -1,下次 meta 重连;launcher 不在 → connect 失败 → 静默。
* 线程安全:g_fd 读写经互斥;后台线程只在 poll/读失败时改 fd,meta 线程只在 connect 时改 fd。
*/
#include "meow_ui_media.h"
#include "meow_ipc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
static char g_app[MEOW_APP_ID_MAX] = "";
static int g_app_set = 0;
static int g_fd = -1;
static pthread_t g_thr;
static int g_thr_on = 0;
static volatile int g_quit = 0;
static meow_media_playctl_fn g_cb = NULL;
/* 后台线程主体(定义在文件后段;on_playctl 懒起时引用)。 */
static void *media_thread_main(void *arg);
static const char *sock_path(void)
{
const char *e = getenv("MEOW_IPC_SOCK");
return (e && e[0]) ? e : MEOW_IPC_PATH;
}
static int connect_path(const char *path)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, path, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
return fd;
}
/* 写满 n 字节(EINTR 重试);失败返回 0。 */
static int write_all(int fd, const uint8_t *p, size_t n)
{
size_t off = 0;
while (off < n) {
ssize_t w = write(fd, p + off, n - off);
if (w < 0) { if (errno == EINTR) continue; return 0; }
if (w == 0) return 0;
off += (size_t)w;
}
return 1;
}
static int read_all(int fd, uint8_t *p, size_t n)
{
size_t off = 0;
while (off < n) {
ssize_t r = read(fd, p + off, n - off);
if (r < 0) { if (errno == EINTR) continue; return 0; }
if (r == 0) return 0;
off += (size_t)r;
}
return 1;
}
/* 组装并写出 16B(LE)帧头 + 负载。 */
static int send_frame(int fd, uint16_t type, uint32_t req_id,
const void *payload, uint32_t len)
{
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC;
uint16_t ver = MEOW_IPC_VERSION;
memcpy(hdr + 0, &magic, 4);
memcpy(hdr + 4, &ver, 2);
memcpy(hdr + 6, &type, 2);
memcpy(hdr + 8, &req_id, 4);
memcpy(hdr + 12, &len, 4);
if (!write_all(fd, hdr, sizeof hdr)) return -1;
if (len && payload && !write_all(fd, payload, len)) return -1;
return 0;
}
/* 读一帧头+负载;返回负载长(≥0),-1=断/坏帧。 */
static int read_frame(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap)
{
uint8_t hdr[MEOW_IPC_HDR_LEN];
if (!read_all(fd, hdr, sizeof hdr)) return -1;
uint32_t magic, len; uint16_t ver;
memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(type, hdr + 6, 2);
memcpy(&len, hdr + 12, 4);
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) return -1;
if (len > rcap) len = rcap;
if (len > 0 && !read_all(fd, rbuf, len)) return -1;
return (int)len;
}
static void media_app_id(void)
{
if (g_app_set) return;
const char *s = getenv("MEOW_LOG_APP");
snprintf(g_app, sizeof g_app, "%s", (s && s[0]) ? s : "app");
g_app_set = 1;
}
/* (调用方已持 mu)连接 + HELLO(MF_PLAYBACK) + 读 ACK。成功存 g_fd 返回 fd;失败 -1。 */
static int conn_locked(void)
{
if (g_fd >= 0) return g_fd;
int fd = connect_path(sock_path());
if (fd < 0) return -1;
media_app_id();
m_hello_t h;
memset(&h, 0, sizeof h);
snprintf(h.app_id, sizeof h.app_id, "%s", g_app);
h.version = 1;
h.flags = MF_PLAYBACK;
if (send_frame(fd, MSG_HELLO, 1, &h, sizeof h) != 0) { close(fd); return -1; }
uint8_t rbuf[256];
uint16_t rt;
if (read_frame(fd, &rt, rbuf, sizeof rbuf) < 0 ||
(rt != MSG_ACK && rt != MSG_ERR)) { close(fd); return -1; }
g_fd = fd;
return fd;
}
/* (调用方已持 mu)标记断线并关闭。 */
static void mark_dead_locked(void)
{
if (g_fd >= 0) { close(g_fd); g_fd = -1; }
}
void meow_ui_media_init(const char *app_id)
{
pthread_mutex_lock(&g_mu);
if (app_id && app_id[0]) {
snprintf(g_app, sizeof g_app, "%s", app_id);
g_app_set = 1;
} else if (!g_app_set) {
const char *s = getenv("MEOW_LOG_APP");
snprintf(g_app, sizeof g_app, "%s", (s && s[0]) ? s : "app");
g_app_set = 1;
}
pthread_mutex_unlock(&g_mu);
}
void meow_ui_media_on_playctl(meow_media_playctl_fn cb)
{
pthread_mutex_lock(&g_mu);
g_cb = cb;
int need_start = !g_thr_on;
if (need_start) { g_quit = 0; g_thr_on = 1; }
pthread_mutex_unlock(&g_mu);
if (need_start) pthread_create(&g_thr, NULL, media_thread_main, NULL);
/* detach:App 退出经 meow_ui_media_stop 设 g_quit 后线程自身退出,无需 join */
if (need_start) pthread_detach(g_thr);
}
void meow_ui_media_meta(uint32_t state, uint32_t pos_ms, uint32_t dur_ms,
const char *title, const char *artist, const char *album,
const char *artwork, const char *lyric)
{
m_meta_t mm;
memset(&mm, 0, sizeof mm);
pthread_mutex_lock(&g_mu);
media_app_id();
snprintf(mm.app_id, sizeof mm.app_id, "%s", g_app);
mm.state = state;
mm.pos_ms = pos_ms;
mm.dur_ms = dur_ms;
if (title) snprintf(mm.title, sizeof mm.title, "%s", title);
if (artist) snprintf(mm.artist, sizeof mm.artist, "%s", artist);
if (album) snprintf(mm.album, sizeof mm.album, "%s", album);
if (artwork && artwork[0]) { snprintf(mm.artwork, sizeof mm.artwork, "%s", artwork); mm.flags |= MF_ART | MF_COVER; }
if (lyric && lyric[0]) { snprintf(mm.lyric, sizeof mm.lyric, "%s", lyric); mm.flags |= MF_LYRIC; }
int fd = conn_locked();
if (fd >= 0) {
if (send_frame(fd, MSG_META, (uint32_t)((uint64_t)pos_ms & 0x7FFFFFFF), &mm, sizeof mm) != 0) {
mark_dead_locked(); /* 断线:下次 meta 重连 */
fd = conn_locked();
if (fd >= 0) (void)send_frame(fd, MSG_META, (uint32_t)((uint64_t)pos_ms & 0x7FFFFFFF), &mm, sizeof mm);
}
}
pthread_mutex_unlock(&g_mu);
}
void meow_ui_media_stop(void)
{
pthread_mutex_lock(&g_mu);
g_quit = 1;
mark_dead_locked();
g_thr_on = 0;
pthread_mutex_unlock(&g_mu);
}
/* ---- 物理媒体键 → 系统语义(M2.xx:SDK 输入层接管,App 前台也生效)----
* 物理键是系统资源(APPS.md §3.4.1):播放/暂停/上一曲/下一曲 → 转发到当前媒体会话。
* 本进程注册过 playctl 回调(= 本 App 是媒体源)→ 直接本地回调(最简,无 IPC 往返);
* 否则经 IPC 发 MSG_PLAYCTL 给 launcher 转发到「最近活跃媒体会话」。best-effort 绝不崩。 */
/* 物理媒体键 keycode → MSG_PLAYCTL cmd(1=播放 2=暂停 3=上一首 4=下一首 5=seek 6=播放/暂停切换)。
* 播放/暂停(164)恒发 cmd=6 toggle——SDK 本地不再按「最近上报 state」判 play/pause:
* 走自有 IPC 线程的媒体源(如 ncm 的 ipc_media.c)不经 meow_ui_media_meta 上报,
* 本地 state 恒 0 → 恒发「播放」,播放中按键无效(真机 bug)。toggle 由媒体源按自身
* 当前态翻转(权威在源)。上一曲 165/19→3,下一曲 163→4。非媒体键返回 -1。 */
static int media_key_cmd(int code)
{
switch (code) {
case 164: /* KEY_PLAYPAUSE:toggle(源侧按当前态翻转) */
return 6;
case 165: case 19: /* KEY_PREVIOUSSONG / 旧码 */
return 3;
case 163: /* KEY_NEXTSONG */
return 4;
default:
return -1;
}
}
/* 控制器路径:发 MSG_PLAYCTL 给 launcher 转发到最近活跃媒体会话(launcher ipc_dispatch
* MSG_PLAYCTL → meow_media_playctl → 下发到当前源 fd)。
* 专门开一条临时连接(不复用媒体源持久连接 g_fd,避免与后台线程 poll/读竞争);
* 无 launcher/断线 → 静默降级(绝不崩)。 */
static void send_playctl_upstream(int cmd, int64_t seek_ms)
{
int fd = connect_path(sock_path());
if (fd < 0) return;
m_playctl_t c;
memset(&c, 0, sizeof c);
c.cmd = (uint32_t)cmd;
c.seek_ms = seek_ms;
if (send_frame(fd, MSG_PLAYCTL, 1, &c, sizeof c) != 0) { close(fd); return; }
/* 读 ACK(best-effort):带 200ms poll,防 launcher 挂死导致输入线程阻塞。 */
struct pollfd p;
p.fd = fd; p.events = POLLIN; p.revents = 0;
if (poll(&p, 1, 200) > 0 && (p.revents & POLLIN)) {
uint8_t rbuf[128];
uint16_t rt;
(void)read_frame(fd, &rt, rbuf, sizeof rbuf);
}
close(fd);
}
/* 物理媒体键按下边沿(val==1 时由输入线程调用;重复/抬起不重复触发)。 */
void meow_ui_media_sys_key(int code)
{
pthread_mutex_lock(&g_mu);
meow_media_playctl_fn cb = g_cb;
pthread_mutex_unlock(&g_mu);
int cmd = media_key_cmd(code);
if (cmd <= 0) return;
if (cb) { cb(cmd, 0); return; } /* 本 App 是媒体源(注册过回调)→ 本地回调 */
send_playctl_upstream(cmd, 0); /* 否则经 IPC 发 launcher 转发到最近活跃会话 */
}
/* 后台线程主体:懒连 + poll 读 launcher 下发 MSG_PLAYCTL → 调 cb。 */
static void *media_thread_main(void *arg)
{
(void)arg;
while (!g_quit) {
pthread_mutex_lock(&g_mu);
int fd = g_fd;
if (fd < 0) fd = conn_locked(); /* 懒连/重连(best-effort;失败则回到循环再试) */
pthread_mutex_unlock(&g_mu);
if (fd < 0) { usleep(500000); continue; }
struct pollfd p;
p.fd = fd; p.events = POLLIN; p.revents = 0;
int pr = poll(&p, 1, 200);
if (pr < 0 && errno != EINTR) {
pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu);
continue;
}
if (pr > 0 && (p.revents & (POLLHUP | POLLERR | POLLNVAL))) {
pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu);
continue;
}
if (pr > 0 && (p.revents & POLLIN)) {
uint8_t rbuf[MEOW_IPC_MAX];
uint16_t type;
int n = read_frame(fd, &type, rbuf, sizeof rbuf);
if (n < 0) {
pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu);
continue;
}
if (type == MSG_PLAYCTL && n >= (int)sizeof(m_playctl_t)) {
m_playctl_t c;
memcpy(&c, rbuf, sizeof c);
/* g_cb 经互斥读快照后调用(App 回调可能在 UI 线程,简化为直接调) */
pthread_mutex_lock(&g_mu);
meow_media_playctl_fn cb = g_cb;
pthread_mutex_unlock(&g_mu);
if (cb) cb((int)c.cmd, (int64_t)c.seek_ms);
}
}
}
pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu);
return NULL;
}