Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/play/ipc_media.c
T
MeowOS 820d1e52bf 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

220 lines
7.3 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 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;
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;
}
int ipc_media_start(void)
{
if (g_thr_on) return 0;
g_quit = 0;
if (pthread_create(&g_thr, NULL, 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;
}