Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/play/player_manager.c
T

322 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.
/* play/player_manager.c — 播放线程:URL 获取(Hi-res 逐档降级)→缓存(命中直接播/否则流式落盘)→
* minimp3 解码→audio_out 直写。UI 不卡(网络+解码全在此线程)。
*
* 模型:一个持久的播放线程。外层循环取「目标曲目」,run_song() 负责一首歌的
* 「下载(缓冲) → 解码 → 输出 → 结束/被指令打断」。暂停=冻结(停读停写停推 pos,状态置 PAUSED);
* seek=重开解码器+输出并 seek 到目标 ms(字节 offset 近似);下一/上一首=打断当前 run_song 返回外层。
* 所有跨线程共享状态在 g_play (mu)。g_st.ctx 的 device_id/cookie/cache 在播放期间稳定(登出会先停播)。
* 返回码/错误经 g_play.err/errmsg 供 UI 提示。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "minimp3_ex.h"
static pthread_t g_thr;
static int g_thr_on;
static int g_want_run = 1; /* 线程运行标志(quit=0) */
#define PCM_CHUNK 8192 /* 每块交织样本数(双声道 → 4096 帧) */
/* ---- 线程内部一次性歌的状态 ---- */
static char g_path[512]; /* 本地 mp3 路径(缓存) */
static int64_t g_file_bytes;
static int g_level; /* 音质档(来自设置) */
static int g_cache_gb;
int player_manager_state(void) { int s; pthread_mutex_lock(&g_play.mu); s = g_play.state; pthread_mutex_unlock(&g_play.mu); return s; }
int player_manager_playing_song(void) { int s; pthread_mutex_lock(&g_play.mu); s = g_play.cur_song; pthread_mutex_unlock(&g_play.mu); return s; }
int64_t player_manager_pos_ms(void) { int64_t p; pthread_mutex_lock(&g_play.mu); p = g_play.pos_ms; pthread_mutex_unlock(&g_play.mu); return p; }
int64_t player_manager_dur_ms(void) { int64_t d; pthread_mutex_lock(&g_play.mu); d = g_play.dur_ms; pthread_mutex_unlock(&g_play.mu); return d; }
int player_manager_error(void) { int e; pthread_mutex_lock(&g_play.mu); e = g_play.err; pthread_mutex_unlock(&g_play.mu); return e; }
void player_manager_set_quality(int level, int cache_gb)
{
g_level = level;
g_cache_gb = cache_gb;
pthread_mutex_lock(&g_play.mu);
g_play.err = 0;
ncm_cache_set_limit_gb(&g_st.ctx.cache, cache_gb);
pthread_mutex_unlock(&g_play.mu);
}
void player_manager_play(int song_idx)
{
pthread_mutex_lock(&g_play.mu);
g_play.cur_song = song_idx;
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
g_play.pos_ms = 0;
g_play.seq++;
pthread_mutex_unlock(&g_play.mu);
}
void player_manager_pause(int on)
{
pthread_mutex_lock(&g_play.mu);
if (on) { if (g_play.state == NCM_PC_PLAYING) g_play.state = NCM_PC_PAUSED; }
else { if (g_play.state == NCM_PC_PAUSED) g_play.state = NCM_PC_PLAYING; }
pthread_mutex_unlock(&g_play.mu);
}
void player_manager_seek(int64_t ms)
{
pthread_mutex_lock(&g_play.mu);
g_play.cmd = NCM_CTL_SEEK;
g_play.cmd_seek_ms = ms;
pthread_mutex_unlock(&g_play.mu);
}
void player_manager_next(int dir)
{
pthread_mutex_lock(&g_play.mu);
int c = g_play.cur_song + dir;
if (c < 0) c = 0;
if (c >= g_st.song_count) c = g_st.song_count - 1;
if (c != g_play.cur_song) {
g_play.cur_song = c;
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
g_play.pos_ms = 0;
g_play.seq++;
}
pthread_mutex_unlock(&g_play.mu);
}
void player_manager_stop(void)
{
pthread_mutex_lock(&g_play.mu);
g_play.cur_song = -1;
g_play.state = NCM_PC_STOPPED;
g_play.pos_ms = 0;
g_play.seq++;
pthread_mutex_unlock(&g_play.mu);
}
/* ---- 每首歌的播放体(在外层循环内调用)---- */
static int download_cb(void *ctx, const unsigned char *data, size_t len)
{
return ncm_cache_write((ncm_cache_writer_t *)ctx, data, len);
}
/* 得到本地 mp3 路径 + 文件大小。命中缓存直接返回;否则 URL 降级 + 流式落缓存。
* 返回 NCM_OK 或负错误码。 */
static int song_local_file(int idx, char *path, size_t psz, int64_t *bytes)
{
const ncm_song_t *s = &g_st.songs[idx];
const char *id = s->id;
/* 缓存命中 → 离线直接播 */
if (ncm_cache_lookup(&g_st.ctx.cache, id, ".mp3", path, psz) == NCM_OK) {
struct stat st;
if (stat(path, &st) == 0) { *bytes = (int64_t)st.st_size; return NCM_OK; }
}
/* 播放地址:从 g_level 降级,只收 mp3 */
ncm_play_url_t u;
int rc = ncm_api_song_url_auto(g_st.ctx.device_id, g_st.ctx.cookie, id, g_level, &u);
if (rc != NCM_OK) return rc;
if (!u.url[0]) return NCM_ERR_NOTFOUND;
/* 流式下载到缓存 temp → commit(稳妥者:先落盘再播) */
ncm_cache_writer_t *w = NULL;
rc = ncm_cache_open(&g_st.ctx.cache, id, ".mp3", &w);
if (rc != NCM_OK) return rc;
ncm_http_req_t req;
memset(&req, 0, sizeof req);
req.url = u.url;
req.method = "GET";
req.extra_hdrs = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
req.verify_ca = NCM_HTTP_VERIFY_DEFAULT;
req.timeout_ms = 30000;
ncm_http_resp_t resp;
size_t maxb = (u.size > 0 && u.size < (64LL << 20)) ? (size_t)u.size : (64u << 20);
rc = ncm_http_download(&req, download_cb, w, maxb, &resp);
if (rc != NCM_OK || resp.status != 200) {
ncm_cache_abort(w);
return (rc != NCM_OK) ? rc : NCM_ERR_HTTP;
}
rc = ncm_cache_commit(w);
if (rc != 0) return rc;
if (ncm_cache_lookup(&g_st.ctx.cache, id, ".mp3", path, psz) != NCM_OK)
return NCM_ERR_NOENT;
struct stat st;
if (stat(path, &st) == 0) *bytes = (int64_t)st.st_size;
return NCM_OK;
}
static void set_err(int err, const char *msg)
{
pthread_mutex_lock(&g_play.mu);
g_play.err = err;
if (msg) { ncm_strlcpy(g_play.errmsg, msg, sizeof g_play.errmsg); }
g_play.state = NCM_PC_STOPPED;
pthread_mutex_unlock(&g_play.mu);
}
/* 一首歌:下载→解码→输出,直到结束或被打断。返回后由外层处理下一首。 */
static void run_song(int idx)
{
int64_t file_bytes = 0;
char path[512];
int rc = song_local_file(idx, path, sizeof path, &file_bytes);
if (rc != NCM_OK) {
const char *m = (rc == NCM_ERR_NOTFOUND) ? "no stream (VIP/copyright)"
: (rc == NCM_ERR_AUTH) ? "session expired" : "load failed";
set_err(rc, m);
return;
}
ncm_strlcpy(g_path, path, sizeof g_path);
g_file_bytes = file_bytes;
const ncm_song_t *s = &g_st.songs[idx];
int64_t dur = s->duration_ms > 0 ? s->duration_ms : 0;
mp3dec_ex_t dec;
memset(&dec, 0, sizeof dec);
if (mp3dec_ex_open(&dec, path, MP3D_SEEK_TO_BYTE) != 0) { set_err(NCM_ERR_IO, "decode open failed"); return; }
/* 音频输出:解码器输出 S16_LE(minimp3 默认);用 44100/2ch 直开 */
if (audio_out_open(44100, 2, 16) != 0) { mp3dec_ex_close(&dec); set_err(NCM_ERR_IO, "audio open failed"); return; }
/* 状态→播放 */
pthread_mutex_lock(&g_play.mu);
g_play.state = NCM_PC_PLAYING;
g_play.pos_ms = 0;
g_play.dur_ms = dur;
g_play.err = 0;
pthread_mutex_unlock(&g_play.mu);
mp3d_sample_t pcm[PCM_CHUNK];
int64_t frames_done = 0; /* 已输出帧(每通道) */
int need_seek_ms = -1;
for (;;) {
if (g_app_quit) break;
/* 取命令 */
pthread_mutex_lock(&g_play.mu);
int cmd = g_play.cmd; g_play.cmd = 0;
int64_t seek_ms = g_play.cmd_seek_ms;
int cur = g_play.cur_song;
int st = g_play.state;
pthread_mutex_unlock(&g_play.mu);
if (cur != idx) break; /* 切歌 → 回外层 */
if (st == NCM_PC_STOPPED) break;
if (cmd == NCM_CTL_SEEK && seek_ms >= 0) { need_seek_ms = (int)seek_ms; }
if (st == NCM_PC_PAUSED) { usleep(40 * 1000); continue; } /* 冻结 */
if (need_seek_ms >= 0) {
/* 重开并 seek:关闭后重开(字节 offset 近似) */
audio_out_close();
mp3dec_ex_close(&dec);
memset(&dec, 0, sizeof dec);
if (mp3dec_ex_open(&dec, path, MP3D_SEEK_TO_BYTE) != 0) { set_err(NCM_ERR_IO, "seek reopen failed"); break; }
if (audio_out_open(44100, 2, 16) != 0) { set_err(NCM_ERR_IO, "audio reopen failed"); break; }
int64_t target = (g_file_bytes > 0 && dur > 0)
? (int64_t)g_file_bytes * need_seek_ms / dur : 0;
mp3dec_ex_seek(&dec, (uint64_t)(target > 0 ? target : 0));
frames_done = (int64_t)need_seek_ms * 44100 / 1000;
need_seek_ms = -1;
continue;
}
size_t got = mp3dec_ex_read(&dec, pcm, PCM_CHUNK);
if (got == 0) break; /* 播完 */
int frames = (int)(got / 2);
if (audio_out_write(pcm, frames) != frames) break;
frames_done += frames;
/* 更新进度(~200ms 节流)*/
int64_t pos = frames_done * 1000 / 44100;
pthread_mutex_lock(&g_play.mu);
g_play.pos_ms = pos;
if (dur > 0 && pos >= dur) g_play.pos_ms = dur;
pthread_mutex_unlock(&g_play.mu);
}
audio_out_close();
mp3dec_ex_close(&dec);
/* 若应继续播下一首且非因命令打断,由外层处理(外层见 cur==idx 且需自然结束) */
}
/* 外层播放线程循环 */
static void *player_thread(void *arg)
{
(void)arg;
while (g_want_run && !g_app_quit) {
pthread_mutex_lock(&g_play.mu);
int cur = g_play.cur_song;
int st = g_play.state;
pthread_mutex_unlock(&g_play.mu);
if (cur < 0 || cur >= g_st.song_count || st == NCM_PC_STOPPED) {
usleep(60 * 1000);
continue;
}
/* 以本地快照播放(idx 被外层线程独占,无需再锁) */
run_song(cur);
pthread_mutex_lock(&g_play.mu);
int cur2 = g_play.cur_song;
int st2 = g_play.state;
pthread_mutex_unlock(&g_play.mu);
/* 自然播完且无新命令 → 顺延下一首;否则回到循环等待 */
if (cur2 >= 0 && cur2 < g_st.song_count && st2 != NCM_PC_STOPPED && cur2 == cur) {
int nx = cur + 1;
if (nx < g_st.song_count) { player_manager_next(1); }
else { player_manager_stop(); }
} else {
usleep(50 * 1000);
}
}
return NULL;
}
/* 为当前曲目拉歌词并解析(UI 线程调):buf 有界,解析后存 lyric 模块。 */
int player_manager_load_lyric(void)
{
lyric_clear();
int idx = player_manager_playing_song();
if (idx < 0 || idx >= g_st.song_count) return NCM_ERR_ARG;
const ncm_song_t *s = &g_st.songs[idx];
char *lrc = (char *)malloc(64 * 1024);
char *tl = (char *)malloc(32 * 1024);
if (!lrc || !tl) { free(lrc); free(tl); return NCM_ERR_OOM; }
int rc = ncm_api_lyric(g_st.ctx.device_id, g_st.ctx.cookie, s->id, lrc, 64 * 1024, tl, 32 * 1024);
if (rc == NCM_OK) lyric_parse(lrc, tl);
free(lrc);
free(tl);
return rc;
}
int player_manager_start(void)
{
if (g_thr_on) return 0;
g_want_run = 1;
g_level = g_st.level;
g_cache_gb = g_st.cache_limit_gb;
pthread_mutex_init(&g_play.mu, NULL);
if (pthread_create(&g_thr, NULL, player_thread, NULL) != 0) return -1;
g_thr_on = 1;
return 0;
}
void player_manager_stop_thread(void)
{
g_want_run = 0;
g_app_quit = 1;
audio_out_close(); /* 打断阻塞写 */
if (g_thr_on) {
pthread_join(g_thr, NULL);
g_thr_on = 0;
}
}