feat(ncm): 播放模式三态(随机/列表循环/单曲循环,播放页三键左)+当前实时播放列表(三键右,歌单滑窗/meow_listview虚拟化分页至5194首,当前曲高亮,点播切歌) — 模式持久化,宿主modetest/queuetest全过

This commit is contained in:
AskaEth committed 2026-09-03 18:51:28 +08:00
1 parent 29e13b2b5e
commit 7ca3e5bd84
8 files changed
+707 -28

No files matched your search

+139 -20
View File
@@ -49,6 +49,63 @@ static int g_cache_gb;
* 同曲护栏据它分流:pending=1 走重播(换档),pending=0 只聚焦不重播(点正在播的曲)。 */
static int g_pending_replay;
/* ---- 播放模式三态(REPORT §NEW:随机 / 列表循环 / 单曲循环)----
* 运行时态在 g_play.play_mode(mu 保护,播放线程读);持久化走 g_st.play_mode(appstate)。
* 随机下一首用 xorshift32(种懒初始化自 ncm_now_ms ^ 本变量地址,避免恒同序列);
* 随机「上一首」用有界历史栈回溯(next/自动顺延 压栈当前曲目,prev 弹栈回放)。 */
static uint32_t g_shuffle_seed;
#define SHUFFLE_HIST_DEPTH 32
static int g_shuffle_hist[SHUFFLE_HIST_DEPTH];
static int g_shuffle_hist_len;
/* 随机取一个 ≠cur 的队列下标(队列>1 时;队列≤1 时返回 cur——单曲等价重播)。持 g_play.mu。 */
static int shuffle_pick(int cur, int cnt)
{
if (cnt <= 1) return (cur >= 0 && cur < cnt) ? cur : 0;
if (g_shuffle_seed == 0)
g_shuffle_seed = ((uint32_t)ncm_now_ms() ^ (uint32_t)(uintptr_t)&g_shuffle_seed) | 1u;
uint32_t x = g_shuffle_seed;
x ^= x << 13; x ^= x >> 17; x ^= x << 5;
g_shuffle_seed = x;
int r = (int)(x % (uint32_t)cnt);
if (r < 0) r = 0;
if (r == cur) r = (r + 1) % cnt;
return r;
}
/* 随机历史压栈/弹栈(持 g_play.mu;换模式时清空)。 */
static void shuffle_hist_push(int cur)
{
if (cur < 0) return;
if (g_shuffle_hist_len >= SHUFFLE_HIST_DEPTH) {
memmove(g_shuffle_hist, g_shuffle_hist + 1,
(size_t)(SHUFFLE_HIST_DEPTH - 1) * sizeof(int));
g_shuffle_hist_len = SHUFFLE_HIST_DEPTH - 1;
}
g_shuffle_hist[g_shuffle_hist_len++] = cur;
}
static int shuffle_hist_pop(void)
{
return (g_shuffle_hist_len > 0) ? g_shuffle_hist[--g_shuffle_hist_len] : -1;
}
int player_manager_play_mode(void)
{
int m; pthread_mutex_lock(&g_play.mu); m = g_play.play_mode; pthread_mutex_unlock(&g_play.mu);
return m;
}
void player_manager_set_play_mode(int m)
{
if (m < NCM_MODE_SHUFFLE || m >= NCM_MODE_COUNT) return;
pthread_mutex_lock(&g_play.mu);
if (m != g_play.play_mode) {
g_play.play_mode = m;
g_shuffle_hist_len = 0; /* 换模式清随机历史(上一首不再回溯旧队列) */
}
pthread_mutex_unlock(&g_play.mu);
}
/* 被抢占回调(SDK 读线程分发;audiod 后开流赢):走 g_play 命令通道暂停(内部持锁),
* 不直接动锁外状态。恢复时 audio_out_pause(0) 会检测流死并自动重开(见 audio_out.c)。 */
static void on_audio_preempt(const char *by_app, void *ctx)
@@ -146,28 +203,88 @@ void player_manager_seek(int64_t ms)
pthread_mutex_unlock(&g_play.mu);
}
/* 落地一个新播放意图(cur=idx):置 BUFFER + 清进度/错误/下载统计 + seq++(新代际,§25)。
* 持 g_play.mu。idx 可与当前 cur 相同(单曲循环重播本曲:seq++ 触 run_song 重跑换档)。 */
static void start_intent(int idx)
{
g_pending_replay = 0; /* 新播放意图落地:清「切档重播」待办 */
g_play.cur_song = idx;
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
g_play.pos_ms = 0;
g_play.err = 0;
g_play.dl_done = 0;
g_play.dl_total = 0;
g_play.seq++;
g_play.eff_level = -1; /* 切歌:生效档随新曲重估(下首恢复用户档,§20) */
g_play.underruns = 0;
g_play.underrun_ms = 0;
g_play.stalled = 0;
g_play.refaults = 0;
}
/* 手动 下一首(dir=+1) / 上一首(dir=-1):按播放模式定语义。
* - 列表循环/单曲循环:顺序 ±1(队列内回绕)。
* - 随机:下一首=随机另一首(并把当前曲压入历史);上一首=历史回溯(栈空则随机另一首)。
* 队列空/未物化(cur 越界)时不改状态。持 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;
int mode = g_play.play_mode;
int c = g_play.cur_song;
int cnt = appstate_queue_count(); /* §23:按队列来源(窗口全量 或 搜索条数) */
if (c >= cnt) c = cnt - 1;
if (c != g_play.cur_song) {
g_pending_replay = 0; /* 新播放意图落地:清「切档重播」待办 */
g_play.cur_song = c;
g_play.cmd = 0;
g_play.state = NCM_PC_BUFFER;
int newc = -1;
if (cnt > 0) {
if (dir > 0) {
if (mode == NCM_MODE_SHUFFLE) {
shuffle_hist_push(c);
newc = shuffle_pick(c, cnt);
} else {
newc = (c < 0) ? 0 : ((c + 1) % cnt); /* 列表/单曲:顺序下一首(回绕) */
}
} else {
if (mode == NCM_MODE_SHUFFLE) {
int h = shuffle_hist_pop();
newc = (h >= 0 && h < cnt) ? h : shuffle_pick(c, cnt);
} else {
newc = (c < 0) ? 0 : ((c - 1 + cnt) % cnt); /* 列表/单曲:顺序上一首(回绕) */
}
}
}
if (newc >= 0 && newc < cnt && newc != c)
start_intent(newc);
pthread_mutex_unlock(&g_play.mu);
}
/* 曲目自然播完(decode 循环退出、state 仍 PLAYING)→ 按播放模式决定下一首:
* - 单曲循环:重播本曲(start_intent(c),seq++ → run_song 重跑)。
* - 列表循环:顺序下一首(队尾回绕队首)。
* - 随机:随机另一首(并压栈当前曲供上一首回溯)。
* 队列空/cur 非法 → 停止。持 g_play.mu 外调用(内部自锁)。 */
void player_manager_next_intent(void)
{
pthread_mutex_lock(&g_play.mu);
int mode = g_play.play_mode;
int c = g_play.cur_song;
int cnt = appstate_queue_count();
if (cnt <= 0 || c < 0 || c >= cnt) {
/* 无可续播目标:停止(清态) */
g_pending_replay = 0;
g_play.cur_song = -1;
g_play.state = NCM_PC_STOPPED;
g_play.pos_ms = 0;
g_play.err = 0;
g_play.dl_done = 0;
g_play.dl_total = 0;
g_play.play_song_valid = 0;
g_play.seq++;
g_play.eff_level = -1; /* 切歌:生效档随新曲重估(下首恢复用户档,§20) */
g_play.underruns = 0;
g_play.underrun_ms = 0;
g_play.stalled = 0;
g_play.refaults = 0;
pthread_mutex_unlock(&g_play.mu);
return;
}
if (mode == NCM_MODE_SINGLE) {
start_intent(c); /* 单曲循环:重播本曲 */
} else if (mode == NCM_MODE_LISTLOOP) {
start_intent((c + 1) % cnt); /* 列表循环:顺序下一首(回绕) */
} else {
shuffle_hist_push(c); /* 随机:压栈当前曲 + 随机下一首 */
start_intent(shuffle_pick(c, cnt));
}
pthread_mutex_unlock(&g_play.mu);
}
@@ -865,14 +982,13 @@ static void *player_thread(void *arg)
int cur2 = g_play.cur_song;
int st2 = g_play.state;
pthread_mutex_unlock(&g_play.mu);
/* 自然播完(decode 循环正常退出,state 仍 PLAYING)且无新命令 → 顺延下一首;
/* 自然播完(decode 循环正常退出,state 仍 PLAYING)且无新命令 → 按播放模式顺延
* (列表循环=顺序下一首回绕 / 随机=随机下一首 / 单曲循环=重播本曲);
* 若是因同 idx 重播(seq 变、state=BUFFER)返回,则循环顶会按当前 cur/state 重跑
* run_song(§25:切音质后重播同一曲换档生效),**不得**当播完顺延下一首。 */
int qc = appstate_queue_count();
if (cur2 >= 0 && cur2 < qc && st2 == NCM_PC_PLAYING && cur2 == cur) {
int nx = cur + 1;
if (nx < qc) { player_manager_next(1); }
else { player_manager_stop(); }
player_manager_next_intent();
} else {
usleep(50 * 1000);
}
@@ -976,6 +1092,9 @@ int player_manager_start(void)
g_want_run = 1;
g_level = g_st.level;
g_cache_gb = g_st.cache_limit_gb;
g_play.play_mode = g_st.play_mode; /* §NEW:恢复持久化的播放模式(默认列表循环) */
g_shuffle_hist_len = 0;
g_shuffle_seed = 0;
pthread_mutex_init(&g_play.mu, NULL);
meow_audio_on_preempt(on_audio_preempt, NULL); /* 被抢占 → 暂停(SDK 读线程分发) */
mem_watch_start(); /* 边播边监控(防 OOM):起监控线程读 RSS,驱下载降载 */