Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/play/ipc_media.c
T
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

298 lines
11 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.
/* play/ipc_media.c — 媒体会话 IPC 最小客户端(SDK 尚无媒体封装,App 内用 meow_ipc.h 帧格式直连)。
*
* 连接:AF_UNIX SOCK_STREAM 到 MEOW_IPC_SOCK(缺省 /usr/data/meow_ipc.sock)。HELLO(app_id,
* MF_PLAYBACK) 注册为媒体源;之后**同一连接**周期发 MSG_META(~1s,进度随播放推进)并阻塞等
* launcher 下发 MSG_PLAYCTL(播放/暂停/上下曲/seek/toggle)。launcher 经该连接 fd 回发(meow_media.c
* send_playctl 用最近活跃会话的 fd)。无 socket/launcher 不跑 → 优雅跳过(打日志不崩)。
* 线程模型:独立线程(main 起了 meow_ui_run 阻塞;播放线程归 player_manager,本线程只做 IPC 收发)。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "meow_ipc.h"
static pthread_t g_thr;
static int g_thr_on;
static int g_quit;
static volatile int g_req_focus_player = 0; /* 任务3:launcher 请求「回到播放页」(UI 线程消费) */
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;
}
/* 发送一帧(头+负载)。 */
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);
size_t off = 0;
while (off < sizeof hdr) {
ssize_t w = write(fd, hdr + off, sizeof hdr - off);
if (w <= 0) return -1;
off += (size_t)w;
}
if (len && payload) {
off = 0;
while (off < len) {
ssize_t w = write(fd, (const uint8_t *)payload + off, len - off);
if (w <= 0) return -1;
off += (size_t)w;
}
}
return 0;
}
static int read_frame(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap)
{
uint8_t hdr[MEOW_IPC_HDR_LEN];
size_t off = 0;
while (off < sizeof hdr) {
ssize_t r = read(fd, hdr + off, sizeof hdr - off);
if (r <= 0) return -1;
off += (size_t)r;
}
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;
off = 0;
while (off < len) {
ssize_t r = read(fd, rbuf + off, len - off);
if (r <= 0) return -1;
off += (size_t)r;
}
return (int)len;
}
/* 组并发送一条 META。 */
static void send_meta(int fd, uint32_t req_id)
{
m_meta_t mm;
memset(&mm, 0, sizeof mm);
ncm_strlcpy(mm.app_id, "dev.player.ncm", sizeof mm.app_id);
int idx = player_manager_playing_song();
pthread_mutex_lock(&g_play.mu);
int pos = (int)g_play.pos_ms;
int dur = (int)g_play.dur_ms;
int state = g_play.state;
int err = g_play.err;
pthread_mutex_unlock(&g_play.mu);
static char title[128], artist[128], album[128];
title[0] = artist[0] = album[0] = 0;
ncm_song_t sm;
if (idx >= 0 && player_manager_current_song(&sm) == 0) { /* 播放快照(窗口无关,§17) */
snprintf(title, sizeof title, "%s", sm.name);
snprintf(artist, sizeof artist, "%s", sm.art_name);
snprintf(album, sizeof album, "%s", sm.album_name);
}
snprintf(mm.title, sizeof mm.title, "%s", title);
snprintf(mm.artist, sizeof mm.artist, "%s", artist);
snprintf(mm.album, sizeof mm.album, "%s", album);
mm.pos_ms = (uint32_t)(pos < 0 ? 0 : pos);
mm.dur_ms = (uint32_t)(dur < 0 ? 0 : dur);
uint32_t st = 0;
if (err != 0) st = 0;
else if (state == NCM_PC_PLAYING) st = 1;
else if (state == NCM_PC_PAUSED) st = 2;
else if (state == NCM_PC_BUFFER) st = 3;
else st = 0;
mm.state = st;
/* 封面:若已落盘(cover 缓存文件)则提供路径 */
mm.flags = 0;
const char *cp = cover_current_path();
if (cp && cp[0]) {
snprintf(mm.artwork, sizeof mm.artwork, "%s", cp);
mm.flags |= MF_ART | MF_COVER;
}
if (lyric_count() > 0) mm.flags |= MF_LYRIC;
send_frame(fd, MSG_META, req_id, &mm, sizeof mm);
}
static void apply_playctl(const m_playctl_t *c)
{
switch (c->cmd) {
case NCM_CTL_PLAY: player_manager_pause(0); break;
case NCM_CTL_PAUSE: player_manager_pause(1); break;
case NCM_CTL_PREV: player_manager_next(-1); break;
case NCM_CTL_NEXT: player_manager_next(1); break;
case NCM_CTL_SEEK: player_manager_seek(c->seek_ms); break;
case NCM_CTL_TOGGLE:
/* 物理播放/暂停键(SDK 164 恒发 toggle,不再本地判 state):源是状态权威,
* 按当前态翻转(与播放页按钮 ui/player.c 同款判定)。非 PLAYING/PAUSED
* (停止/缓冲)时 pause() 内部按态过滤,天然 no-op 安全。 */
player_manager_pause(player_manager_state() == NCM_PC_PAUSED ? 0 : 1);
break;
case NCM_CTL_FOCUS:
/* 任务3:通知中心媒体卡片点「卡片体」→ launcher 发本 cmd(需要 launcher 接线,见任务书):
* 请求 App 回到播放页。只置标志(跨线程),UI 线程 app_on_tick 消费后导航(page nav 非线程安全)。 */
g_req_focus_player = 1;
break;
default: break;
}
}
static void *ipc_thread(void *arg)
{
(void)arg;
int fd = connect_path(sock_path());
if (fd < 0) { meow_log(MEOW_LOG_WARN, "ipc: no launcher socket"); return NULL; }
m_hello_t h;
memset(&h, 0, sizeof h);
ncm_strlcpy(h.app_id, "dev.player.ncm", sizeof h.app_id);
h.version = 1;
h.flags = MF_PLAYBACK;
if (send_frame(fd, MSG_HELLO, 1, &h, sizeof h) != 0) { close(fd); return NULL; }
uint8_t hb[256];
uint16_t htype;
if (read_frame(fd, &htype, hb, sizeof hb) < 0) { close(fd); return NULL; }
send_meta(fd, 2); /* 首条注册媒体源(state=0 未播也登记,start 播后自动成源) */
uint64_t last_meta = 0;
while (!g_quit && !g_app_quit) {
struct pollfd p;
p.fd = fd; p.events = POLLIN;
int pr = poll(&p, 1, 200);
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) break;
if (type == MSG_PLAYCTL && n >= (int)sizeof(m_playctl_t)) {
m_playctl_t c;
memcpy(&c, rbuf, sizeof c);
apply_playctl(&c);
}
} else if (pr < 0 && errno != EINTR) {
break;
}
uint64_t now = meow_ui_time_ms();
if (now - last_meta >= 1000) {
send_meta(fd, (uint32_t)(now & 0x7FFFFFFF));
last_meta = now;
}
}
close(fd);
return NULL;
}
/* 任务2:home 根页左滑「回桌面后台」——不再进程退出,改发 MSG_NAV{EXIT} 给 launcher。
* launcher M4 据 app.json background_audio + 媒体会话 PLAYING(meow_media_app_playing)判
* 保活(进程+音频流保留转后台,UI 走 §10.5 SDK present 自抑,非 SIGSTOP);否则才真终止。
* 本函数**不**置 g_app_quit、**不**停线程——进程保留,媒体会话继续上报 PLAYING(任务2 ③)。
* 独立连接发一帧(launcher ipc_dispatch MSG_NAV{EXIT} → meow_nav_request_exit,app_launcher.c:571)。
* 返回 0=发出 / -1=无 socket 或写失败。 */
int ipc_media_nav_exit(void)
{
int fd = connect_path(sock_path());
if (fd < 0) {
meow_log(MEOW_LOG_WARN, "ipc: nav_exit no launcher socket");
return -1;
}
m_nav_t n;
memset(&n, 0, sizeof n);
ncm_strlcpy(n.app_id, "dev.player.ncm", sizeof n.app_id);
n.nav_type = MEOW_NAV_EXIT;
n.depth = 0;
int rc = send_frame(fd, MSG_NAV, 9, &n, sizeof n);
close(fd);
meow_log(MEOW_LOG_INFO, "ipc: nav_exit sent rc=%d (request launcher background)", rc);
return rc;
}
/* 任务4 keep_awake:ACT_* 值 → launcher app.json awake/route 键用的活动名。
* 命中的只有这些(ncm_app_internal.h ACT_* 枚举);未知/空栈(0) 返回 NULL(不起作用)。 */
static const char *ncm_act_name(int act)
{
switch (act) {
case ACT_LOGIN: return "login";
case ACT_HOME: return "home";
case ACT_PLAYLIST: return "playlist";
case ACT_PLAYER: return "player";
case ACT_SETTINGS: return "settings";
case ACT_SEARCH: return "search";
default: return NULL;
}
}
/* 任务4 keep_awake:把当前顶层 activity 上报 launcher(MSG_ACT_FOCUS,独立连接发一帧,无需 HELLO)。
* launcher util.c ipc_dispatch MSG_ACT_FOCUS → meow_fg_act_set(app,act);fg_keep_awake() 依
* app.json awake.<act>=1 判定该 activity 是否常亮(超时不自动息屏)。无 socket/launcher 不跑 → 优雅跳过。
* 返回 0=发出 / -1=无 socket 或写失败。 */
int ipc_act_report(int act)
{
const char *name = ncm_act_name(act);
if (!name) return -1;
int fd = connect_path(sock_path());
if (fd < 0) {
meow_log(MEOW_LOG_WARN, "ipc: act_focus(%s) no launcher socket", name);
return -1;
}
m_act_focus_t f;
memset(&f, 0, sizeof f);
ncm_strlcpy(f.app_id, "dev.player.ncm", sizeof f.app_id);
ncm_strlcpy(f.activity, name, sizeof f.activity);
int rc = send_frame(fd, MSG_ACT_FOCUS, 10, &f, sizeof f);
close(fd);
meow_log(MEOW_LOG_INFO, "ipc: act_focus act=%s rc=%d", name, rc);
return rc;
}
/* 任务3:UI 线程消费「回到播放页」标志。返回 1=有未消费的聚焦请求(消费一次)。 */
int ipc_media_focus_consume(void)
{
if (g_req_focus_player) {
g_req_focus_player = 0;
return 1;
}
return 0;
}
int ipc_media_start(void)
{
if (g_thr_on) return 0;
g_quit = 0;
if (ncm_thread_create(&g_thr, ipc_thread, NULL) != 0) return -1;
pthread_detach(g_thr);
g_thr_on = 1;
return 0;
}
void ipc_media_stop(void)
{
g_quit = 1;
/* 线程在 200ms poll 内退出(detach,不 join)*/
g_thr_on = 0;
}