213 lines
6.8 KiB
C
213 lines
6.8 KiB
C
/* 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)。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;
|
||
if (idx >= 0 && idx < g_st.song_count) {
|
||
snprintf(title, sizeof title, "%s", g_st.songs[idx].name);
|
||
snprintf(artist, sizeof artist, "%s", g_st.songs[idx].art_name);
|
||
snprintf(album, sizeof album, "%s", g_st.songs[idx].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;
|
||
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;
|
||
}
|