feat(ncm): 播放模式三态(随机/列表循环/单曲循环,播放页三键左)+当前实时播放列表(三键右,歌单滑窗/meow_listview虚拟化分页至5194首,当前曲高亮,点播切歌) — 模式持久化,宿主modetest/queuetest全过
This commit is contained in:
1 parent
29e13b2b5e
commit
7ca3e5bd84
8 files changed
+707
-28
No files matched your search
@@ -24,7 +24,7 @@ volatile int g_net_ready = 0;
|
|||||||
int g_host_mock = 0;
|
int g_host_mock = 0;
|
||||||
|
|
||||||
static meow_view *g_login_view, *g_home_view, *g_pl_view, *g_plr_view, *g_set_view,
|
static meow_view *g_login_view, *g_home_view, *g_pl_view, *g_plr_view, *g_set_view,
|
||||||
*g_search_view;
|
*g_search_view, *g_queue_view;
|
||||||
|
|
||||||
static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); }
|
static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); }
|
||||||
|
|
||||||
@@ -92,6 +92,7 @@ static void app_on_tap(int x, int y)
|
|||||||
if (player_is_active()) { player_on_tap(x, y); return; }
|
if (player_is_active()) { player_on_tap(x, y); return; }
|
||||||
if (settings_is_active()) { settings_on_tap(x, y); return; }
|
if (settings_is_active()) { settings_on_tap(x, y); return; }
|
||||||
if (playlist_is_active()) { playlist_on_tap(x, y); return; }
|
if (playlist_is_active()) { playlist_on_tap(x, y); return; }
|
||||||
|
if (queue_is_active()) { queue_on_tap(x, y); return; }
|
||||||
if (search_is_active()) { search_on_tap(x, y); return; }
|
if (search_is_active()) { search_on_tap(x, y); return; }
|
||||||
home_on_tap(x, y);
|
home_on_tap(x, y);
|
||||||
}
|
}
|
||||||
@@ -105,6 +106,7 @@ static void app_on_swipe(int dir, int dx, int dy)
|
|||||||
if (player_is_active()) { meow_view_page_pop(); return; }
|
if (player_is_active()) { meow_view_page_pop(); return; }
|
||||||
if (settings_is_active()) { meow_view_page_pop(); return; }
|
if (settings_is_active()) { meow_view_page_pop(); return; }
|
||||||
if (playlist_is_active()) { meow_view_page_pop(); return; }
|
if (playlist_is_active()) { meow_view_page_pop(); return; }
|
||||||
|
if (queue_is_active()) { meow_view_page_pop(); return; }
|
||||||
if (search_is_active()) { meow_view_page_pop(); return; }
|
if (search_is_active()) { meow_view_page_pop(); return; }
|
||||||
if (home_is_active()) {
|
if (home_is_active()) {
|
||||||
/* 任务2:home 根页左滑不再退出进程 → 请求 launcher 退到桌面/后台(进程+媒体会话保留)。
|
/* 任务2:home 根页左滑不再退出进程 → 请求 launcher 退到桌面/后台(进程+媒体会话保留)。
|
||||||
@@ -121,6 +123,7 @@ static void app_on_swipe(int dir, int dx, int dy)
|
|||||||
}
|
}
|
||||||
if (home_is_active()) { home_swipe(dir, dy); return; }
|
if (home_is_active()) { home_swipe(dir, dy); return; }
|
||||||
if (playlist_is_active()) { playlist_swipe(dir, dy); return; }
|
if (playlist_is_active()) { playlist_swipe(dir, dy); return; }
|
||||||
|
if (queue_is_active()) { queue_swipe(dir, dy); return; }
|
||||||
if (search_is_active()) { search_swipe(dir, dy); return; }
|
if (search_is_active()) { search_swipe(dir, dy); return; }
|
||||||
if (player_is_active()) { player_on_swipe(dir, dy); return; }
|
if (player_is_active()) { player_on_swipe(dir, dy); return; }
|
||||||
}
|
}
|
||||||
@@ -130,6 +133,7 @@ static void app_on_touch_down(int x, int y)
|
|||||||
if (player_is_active()) player_touch_down(x, y); /* §22 中部横滑页切换跟踪 */
|
if (player_is_active()) player_touch_down(x, y); /* §22 中部横滑页切换跟踪 */
|
||||||
else if (home_is_active()) home_touch_down(x, y);
|
else if (home_is_active()) home_touch_down(x, y);
|
||||||
else if (playlist_is_active()) playlist_touch_down(x, y);
|
else if (playlist_is_active()) playlist_touch_down(x, y);
|
||||||
|
else if (queue_is_active()) queue_touch_down(x, y);
|
||||||
else if (settings_is_active()) settings_touch_down(x, y);
|
else if (settings_is_active()) settings_touch_down(x, y);
|
||||||
else if (search_is_active()) search_touch_down(x, y);
|
else if (search_is_active()) search_touch_down(x, y);
|
||||||
}
|
}
|
||||||
@@ -139,6 +143,7 @@ static void app_on_touch_move(int x, int y)
|
|||||||
if (player_is_active()) player_touch_move(x, y);
|
if (player_is_active()) player_touch_move(x, y);
|
||||||
else if (home_is_active()) home_touch_move(x, y);
|
else if (home_is_active()) home_touch_move(x, y);
|
||||||
else if (playlist_is_active()) playlist_touch_move(x, y);
|
else if (playlist_is_active()) playlist_touch_move(x, y);
|
||||||
|
else if (queue_is_active()) queue_touch_move(x, y);
|
||||||
else if (settings_is_active()) settings_touch_move(x, y);
|
else if (settings_is_active()) settings_touch_move(x, y);
|
||||||
else if (search_is_active()) search_touch_move(x, y);
|
else if (search_is_active()) search_touch_move(x, y);
|
||||||
}
|
}
|
||||||
@@ -188,6 +193,7 @@ static void app_on_tick(void)
|
|||||||
if (login_is_active()) { login_tick(now); return; }
|
if (login_is_active()) { login_tick(now); return; }
|
||||||
if (player_is_active()) { player_tick(now); return; }
|
if (player_is_active()) { player_tick(now); return; }
|
||||||
if (search_is_active()) { search_tick(now); return; }
|
if (search_is_active()) { search_tick(now); return; }
|
||||||
|
if (queue_is_active()) { queue_tick(now); return; }
|
||||||
if (home_is_active()) { home_tick(now); }
|
if (home_is_active()) { home_tick(now); }
|
||||||
if (playlist_is_active()) { playlist_tick(now); }
|
if (playlist_is_active()) { playlist_tick(now); }
|
||||||
chrome_tick();
|
chrome_tick();
|
||||||
@@ -219,13 +225,14 @@ int main(void)
|
|||||||
ipc_media_start();
|
ipc_media_start();
|
||||||
g_net_ready = 1;
|
g_net_ready = 1;
|
||||||
|
|
||||||
/* 建六页 view */
|
/* 建页 view */
|
||||||
g_login_view = login_view_create();
|
g_login_view = login_view_create();
|
||||||
g_home_view = home_view_create();
|
g_home_view = home_view_create();
|
||||||
g_pl_view = playlist_view_create();
|
g_pl_view = playlist_view_create();
|
||||||
g_plr_view = player_view_create();
|
g_plr_view = player_view_create();
|
||||||
g_set_view = settings_view_create();
|
g_set_view = settings_view_create();
|
||||||
g_search_view = search_view_create();
|
g_search_view = search_view_create();
|
||||||
|
g_queue_view = queue_view_create(); /* §NEW 当前播放队列页 */
|
||||||
mini_bar_init(); /* mini 播放栏:登记跨 Activity 绘制物(进 root 页前,首帧即参与重画) */
|
mini_bar_init(); /* mini 播放栏:登记跨 Activity 绘制物(进 root 页前,首帧即参与重画) */
|
||||||
|
|
||||||
/* 进入根页:已登录→home,否则→login */
|
/* 进入根页:已登录→home,否则→login */
|
||||||
@@ -248,6 +255,7 @@ int main(void)
|
|||||||
meow_view_free(g_home_view);
|
meow_view_free(g_home_view);
|
||||||
meow_view_free(g_login_view);
|
meow_view_free(g_login_view);
|
||||||
meow_view_free(g_search_view);
|
meow_view_free(g_search_view);
|
||||||
|
meow_view_free(g_queue_view);
|
||||||
meow_ui_shutdown();
|
meow_ui_shutdown();
|
||||||
meow_log(MEOW_LOG_INFO, "exit");
|
meow_log(MEOW_LOG_INFO, "exit");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -30,6 +30,17 @@
|
|||||||
#define ACT_PLAYER 3 /* 播放页(可横竖屏切,v1 允许切) */
|
#define ACT_PLAYER 3 /* 播放页(可横竖屏切,v1 允许切) */
|
||||||
#define ACT_SETTINGS 4 /* 设置页 */
|
#define ACT_SETTINGS 4 /* 设置页 */
|
||||||
#define ACT_SEARCH 5 /* 搜索页(顶栏搜索按钮进入) */
|
#define ACT_SEARCH 5 /* 搜索页(顶栏搜索按钮进入) */
|
||||||
|
#define ACT_QUEUE 6 /* 当前播放队列页(播放页「队列」钮进入,展示实时播放列表) */
|
||||||
|
|
||||||
|
/* ---- 播放模式三态(REPORT §NEW:随机 / 列表循环 / 单曲循环)----
|
||||||
|
* 默认=列表循环(延续既有「顺序下一首」语义;列表循环下自动顺延会在队尾回绕)。
|
||||||
|
* 三态按钮点击按 enum 序循环(SHUFFLE→LISTLOOP→SINGLE→SHUFFLE),图标/文案见 ncm_mode_name。 */
|
||||||
|
enum {
|
||||||
|
NCM_MODE_SHUFFLE = 0, /* 随机播放:自动播完/手动下一首 → 随机取队列另一首 */
|
||||||
|
NCM_MODE_LISTLOOP = 1, /* 列表循环:自动播完/手动下一首 → 顺序下一首(队尾回绕队首) */
|
||||||
|
NCM_MODE_SINGLE = 2, /* 单曲循环:当前曲播完 → 重播本曲;手动下一首仍顺序 */
|
||||||
|
};
|
||||||
|
#define NCM_MODE_COUNT 3
|
||||||
|
|
||||||
/* ---- 封面 CDN 尺寸参数(REPORT §NEW 任务2:网易云图片 CDN 对 URL 追加 ?param=WxH 返等比
|
/* ---- 封面 CDN 尺寸参数(REPORT §NEW 任务2:网易云图片 CDN 对 URL 追加 ?param=WxH 返等比
|
||||||
* 缩略 JPEG——省内存 +解 webp 三分型③+命中率高;已含 param= 的 URL 尊重原样)。
|
* 缩略 JPEG——省内存 +解 webp 三分型③+命中率高;已含 param= 的 URL 尊重原样)。
|
||||||
@@ -137,7 +148,8 @@ enum {
|
|||||||
typedef struct ncm_play_run {
|
typedef struct ncm_play_run {
|
||||||
pthread_mutex_t mu;
|
pthread_mutex_t mu;
|
||||||
int state; /* NCM_PC_* */
|
int state; /* NCM_PC_* */
|
||||||
int cur_song; /* 当前曲目在 g_st.songs[] 下标;-1=无 */
|
int play_mode; /* NCM_MODE_*(播放模式三态;UI 读写,播放线程切歌依此) */
|
||||||
|
int cur_song; /* 当前曲目在队列(queue_src)下标;-1=无 */
|
||||||
int64_t pos_ms; /* 当前播放位置(播放进程推进,seek 写此) */
|
int64_t pos_ms; /* 当前播放位置(播放进程推进,seek 写此) */
|
||||||
int64_t dur_ms; /* 当前曲目标称时长 */
|
int64_t dur_ms; /* 当前曲目标称时长 */
|
||||||
int cmd; /* 暂存最新内部命令(play/pause/prev/next/seek)*/
|
int cmd; /* 暂存最新内部命令(play/pause/prev/next/seek)*/
|
||||||
@@ -224,6 +236,7 @@ typedef struct ncm_app_state {
|
|||||||
int level; /* NCM_LEVEL_* */
|
int level; /* NCM_LEVEL_* */
|
||||||
int cache_limit_gb;
|
int cache_limit_gb;
|
||||||
int player_bg_mode; /* 歌词页背景模式(§22):NCM_BG_DEFAULT / NCM_BG_POWER_SAVE */
|
int player_bg_mode; /* 歌词页背景模式(§22):NCM_BG_DEFAULT / NCM_BG_POWER_SAVE */
|
||||||
|
int play_mode; /* 播放模式(§NEW 三态):NCM_MODE_*;持久化 ncm.play_mode */
|
||||||
|
|
||||||
/* 搜索(REPORT §23:独立结果数组,不覆写歌单窗口;单曲/歌单两类页签各自独立结果集/分页/滚动) */
|
/* 搜索(REPORT §23:独立结果数组,不覆写歌单窗口;单曲/歌单两类页签各自独立结果集/分页/滚动) */
|
||||||
char search_query[256];
|
char search_query[256];
|
||||||
@@ -328,6 +341,7 @@ void player_on_tap(int x, int y);
|
|||||||
void player_tick(uint64_t now); /* 播放进度/歌词滚动 */
|
void player_tick(uint64_t now); /* 播放进度/歌词滚动 */
|
||||||
void player_play_song(int idx); /* 从歌单窗口第 idx 首开始播放(queue=PLAYLIST) */
|
void player_play_song(int idx); /* 从歌单窗口第 idx 首开始播放(queue=PLAYLIST) */
|
||||||
void player_play_search_song(int i); /* 从搜索单曲第 i 首开始播放(queue=SEARCH,§23) */
|
void player_play_search_song(int i); /* 从搜索单曲第 i 首开始播放(queue=SEARCH,§23) */
|
||||||
|
void player_play_queue_song(int idx);/* 从「当前实时播放队列」第 idx 首播放(保持当前 queue_src,§NEW) */
|
||||||
void player_pause_toggle(int on);
|
void player_pause_toggle(int on);
|
||||||
void player_next(int dir); /* dir=+1 下一首 / -1 上一首 */
|
void player_next(int dir); /* dir=+1 下一首 / -1 上一首 */
|
||||||
void player_seek(int64_t ms);
|
void player_seek(int64_t ms);
|
||||||
@@ -355,6 +369,16 @@ void mini_bar_shutdown(void); /* 注销(app 退出前) */
|
|||||||
void mini_bar_tick(uint64_t now);/* 每帧:可见性/歌曲/暂停态/进度变化 → 失效栏带并重绘 */
|
void mini_bar_tick(uint64_t now);/* 每帧:可见性/歌曲/暂停态/进度变化 → 失效栏带并重绘 */
|
||||||
int mini_bar_hit(int x, int y); /* app_on_tap 顶层先行拦截:1=消费(toggle/进播放页) */
|
int mini_bar_hit(int x, int y); /* app_on_tap 顶层先行拦截:1=消费(toggle/进播放页) */
|
||||||
|
|
||||||
|
/* queue(当前播放队列页,§NEW:展示实时播放列表/分页/当前曲高亮/点播切歌) */
|
||||||
|
meow_view *queue_view_create(void);
|
||||||
|
int queue_is_active(void);
|
||||||
|
int queue_on_tap(int x, int y);
|
||||||
|
void queue_swipe(int dir, int dist);
|
||||||
|
void queue_touch_down(int x, int y);
|
||||||
|
void queue_touch_move(int x, int y);
|
||||||
|
void queue_open(void); /* 进当前播放队列页 */
|
||||||
|
void queue_tick(uint64_t now); /* UI tick:检查队列 worker 完成并刷新 */
|
||||||
|
|
||||||
/* search */
|
/* search */
|
||||||
meow_view *search_view_create(void);
|
meow_view *search_view_create(void);
|
||||||
int search_is_active(void);
|
int search_is_active(void);
|
||||||
@@ -433,6 +457,7 @@ void appstate_set_queue(int src); /* NCM_QUEUE_PLAYLIST / NCM_QUEU
|
|||||||
int appstate_get_queue(void);
|
int appstate_get_queue(void);
|
||||||
int appstate_queue_count(void); /* 当前播放队列长度(按 queue_src) */
|
int appstate_queue_count(void); /* 当前播放队列长度(按 queue_src) */
|
||||||
int appstate_queue_song(int idx, ncm_song_t *out); /* 队列第 idx 首;0=ok(按 queue_src) */
|
int appstate_queue_song(int idx, ncm_song_t *out); /* 队列第 idx 首;0=ok(按 queue_src) */
|
||||||
|
void appstate_apply_play_mode(int m); /* 设 g_st.play_mode + 落盘(持久化「记住模式」,§NEW) */
|
||||||
|
|
||||||
/* ---- 搜索结果独立数组(§23;g_search_mu 保护,UI/worker 经此访问)---- */
|
/* ---- 搜索结果独立数组(§23;g_search_mu 保护,UI/worker 经此访问)---- */
|
||||||
int appstate_search_count(int tab); /* tab: 0=单曲 1=歌单 */
|
int appstate_search_count(int tab); /* tab: 0=单曲 1=歌单 */
|
||||||
@@ -523,10 +548,14 @@ int player_manager_error(void);
|
|||||||
int player_manager_current_song(ncm_song_t *out); /* 播放曲快照锁内拷贝;0=有效 -1=无(REPORT §17) */
|
int player_manager_current_song(ncm_song_t *out); /* 播放曲快照锁内拷贝;0=有效 -1=无(REPORT §17) */
|
||||||
void player_manager_dl(int64_t *done, int64_t *total); /* 本曲下载进度(字节;total=0 未知,§18) */
|
void player_manager_dl(int64_t *done, int64_t *total); /* 本曲下载进度(字节;total=0 未知,§18) */
|
||||||
int player_manager_eff_level(void); /* 实际生效音质档(§20;-1=未定) */
|
int player_manager_eff_level(void); /* 实际生效音质档(§20;-1=未定) */
|
||||||
|
int player_manager_play_mode(void); /* 当前播放模式(§NEW 三态:NCM_MODE_*) */
|
||||||
|
void player_manager_set_play_mode(int m); /* 设播放模式(运行时态;持久化由 appstate_apply_play_mode 落盘) */
|
||||||
|
void player_manager_next_intent(void); /* 曲目自然播完 → 按播放模式决定下一首/重播(单曲循环重播本曲) */
|
||||||
|
|
||||||
/* 音质档位显示名/规格(ui/chrome.c,§20 八档 UI 权威口径;settings/player 共用) */
|
/* 音质档位显示名/规格(ui/chrome.c,§20 八档 UI 权威口径;settings/player 共用) */
|
||||||
const char *ncm_level_name(int lv); /* 短名:标准/极高 HQ/无损 SQ/Hi-Res/高清臻音/臻音全景声/超清母带/沉浸环绕声 */
|
const char *ncm_level_name(int lv); /* 短名:标准/极高 HQ/无损 SQ/Hi-Res/高清臻音/臻音全景声/超清母带/沉浸环绕声 */
|
||||||
const char *ncm_level_spec(int lv); /* 规格:128kbps / ≤320kbps / 48kHz·16bit / ... */
|
const char *ncm_level_spec(int lv); /* 规格:128kbps / ≤320kbps / 48kHz·16bit / ... */
|
||||||
|
const char *ncm_mode_name(int m); /* 播放模式短名(§NEW 三态):随机 / 列表 / 单曲 */
|
||||||
|
|
||||||
/* songmeta(app/logic/songmeta.c):歌曲元数据本地缓存(<cache_dir>/meta/<id>.bin),
|
/* songmeta(app/logic/songmeta.c):歌曲元数据本地缓存(<cache_dir>/meta/<id>.bin),
|
||||||
* 拉过的 song_detail 结果序列化落盘,再次入窗免重复请求(REPORT §17)。 */
|
* 拉过的 song_detail 结果序列化落盘,再次入窗免重复请求(REPORT §17)。 */
|
||||||
|
|||||||
@@ -49,6 +49,63 @@ static int g_cache_gb;
|
|||||||
* 同曲护栏据它分流:pending=1 走重播(换档),pending=0 只聚焦不重播(点正在播的曲)。 */
|
* 同曲护栏据它分流:pending=1 走重播(换档),pending=0 只聚焦不重播(点正在播的曲)。 */
|
||||||
static int g_pending_replay;
|
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 命令通道暂停(内部持锁),
|
/* 被抢占回调(SDK 读线程分发;audiod 后开流赢):走 g_play 命令通道暂停(内部持锁),
|
||||||
* 不直接动锁外状态。恢复时 audio_out_pause(0) 会检测流死并自动重开(见 audio_out.c)。 */
|
* 不直接动锁外状态。恢复时 audio_out_pause(0) 会检测流死并自动重开(见 audio_out.c)。 */
|
||||||
static void on_audio_preempt(const char *by_app, void *ctx)
|
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);
|
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)
|
void player_manager_next(int dir)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_play.mu);
|
pthread_mutex_lock(&g_play.mu);
|
||||||
int c = g_play.cur_song + dir;
|
int mode = g_play.play_mode;
|
||||||
if (c < 0) c = 0;
|
int c = g_play.cur_song;
|
||||||
int cnt = appstate_queue_count(); /* §23:按队列来源(窗口全量 或 搜索条数) */
|
int cnt = appstate_queue_count(); /* §23:按队列来源(窗口全量 或 搜索条数) */
|
||||||
if (c >= cnt) c = cnt - 1;
|
int newc = -1;
|
||||||
if (c != g_play.cur_song) {
|
if (cnt > 0) {
|
||||||
g_pending_replay = 0; /* 新播放意图落地:清「切档重播」待办 */
|
if (dir > 0) {
|
||||||
g_play.cur_song = c;
|
if (mode == NCM_MODE_SHUFFLE) {
|
||||||
g_play.cmd = 0;
|
shuffle_hist_push(c);
|
||||||
g_play.state = NCM_PC_BUFFER;
|
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.pos_ms = 0;
|
||||||
g_play.err = 0;
|
g_play.play_song_valid = 0;
|
||||||
g_play.dl_done = 0;
|
|
||||||
g_play.dl_total = 0;
|
|
||||||
g_play.seq++;
|
g_play.seq++;
|
||||||
g_play.eff_level = -1; /* 切歌:生效档随新曲重估(下首恢复用户档,§20) */
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
g_play.underruns = 0;
|
return;
|
||||||
g_play.underrun_ms = 0;
|
}
|
||||||
g_play.stalled = 0;
|
if (mode == NCM_MODE_SINGLE) {
|
||||||
g_play.refaults = 0;
|
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);
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
}
|
}
|
||||||
@@ -865,14 +982,13 @@ static void *player_thread(void *arg)
|
|||||||
int cur2 = g_play.cur_song;
|
int cur2 = g_play.cur_song;
|
||||||
int st2 = g_play.state;
|
int st2 = g_play.state;
|
||||||
pthread_mutex_unlock(&g_play.mu);
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
/* 自然播完(decode 循环正常退出,state 仍 PLAYING)且无新命令 → 顺延下一首;
|
/* 自然播完(decode 循环正常退出,state 仍 PLAYING)且无新命令 → 按播放模式顺延
|
||||||
|
* (列表循环=顺序下一首回绕 / 随机=随机下一首 / 单曲循环=重播本曲);
|
||||||
* 若是因同 idx 重播(seq 变、state=BUFFER)返回,则循环顶会按当前 cur/state 重跑
|
* 若是因同 idx 重播(seq 变、state=BUFFER)返回,则循环顶会按当前 cur/state 重跑
|
||||||
* run_song(§25:切音质后重播同一曲换档生效),**不得**当播完顺延下一首。 */
|
* run_song(§25:切音质后重播同一曲换档生效),**不得**当播完顺延下一首。 */
|
||||||
int qc = appstate_queue_count();
|
int qc = appstate_queue_count();
|
||||||
if (cur2 >= 0 && cur2 < qc && st2 == NCM_PC_PLAYING && cur2 == cur) {
|
if (cur2 >= 0 && cur2 < qc && st2 == NCM_PC_PLAYING && cur2 == cur) {
|
||||||
int nx = cur + 1;
|
player_manager_next_intent();
|
||||||
if (nx < qc) { player_manager_next(1); }
|
|
||||||
else { player_manager_stop(); }
|
|
||||||
} else {
|
} else {
|
||||||
usleep(50 * 1000);
|
usleep(50 * 1000);
|
||||||
}
|
}
|
||||||
@@ -976,6 +1092,9 @@ int player_manager_start(void)
|
|||||||
g_want_run = 1;
|
g_want_run = 1;
|
||||||
g_level = g_st.level;
|
g_level = g_st.level;
|
||||||
g_cache_gb = g_st.cache_limit_gb;
|
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);
|
pthread_mutex_init(&g_play.mu, NULL);
|
||||||
meow_audio_on_preempt(on_audio_preempt, NULL); /* 被抢占 → 暂停(SDK 读线程分发) */
|
meow_audio_on_preempt(on_audio_preempt, NULL); /* 被抢占 → 暂停(SDK 读线程分发) */
|
||||||
mem_watch_start(); /* 边播边监控(防 OOM):起监控线程读 RSS,驱下载降载 */
|
mem_watch_start(); /* 边播边监控(防 OOM):起监控线程读 RSS,驱下载降载 */
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ static int page_id(const char *name)
|
|||||||
if (strcmp(name, "searchhist") == 0) return ACT_SEARCH; /* §23 空态=历史列表 */
|
if (strcmp(name, "searchhist") == 0) return ACT_SEARCH; /* §23 空态=历史列表 */
|
||||||
if (strcmp(name, "searchpl") == 0) return ACT_SEARCH; /* §23 歌单页签 */
|
if (strcmp(name, "searchpl") == 0) return ACT_SEARCH; /* §23 歌单页签 */
|
||||||
if (strcmp(name, "searchtap") == 0) return ACT_SEARCH; /* §23 误触抑制自检 */
|
if (strcmp(name, "searchtap") == 0) return ACT_SEARCH; /* §23 误触抑制自检 */
|
||||||
|
if (strcmp(name, "modetest") == 0) return ACT_PLAYER; /* §NEW 播放模式三态逻辑自检 */
|
||||||
|
if (strcmp(name, "queuetest") == 0) return ACT_QUEUE; /* §NEW 播放队列页/点播语义自检 */
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +235,16 @@ int main(int argc, char **argv)
|
|||||||
appstate_search_history_add("Jay \u5553\u8868");
|
appstate_search_history_add("Jay \u5553\u8868");
|
||||||
appstate_search_history_add("lofi beat");
|
appstate_search_history_add("lofi beat");
|
||||||
appstate_search_history_add("Jay \u5553\u8868"); /* 去重 → 仍 3 条 */
|
appstate_search_history_add("Jay \u5553\u8868"); /* 去重 → 仍 3 条 */
|
||||||
|
} else if (strcmp(name, "modetest") == 0 || strcmp(name, "queuetest") == 0) {
|
||||||
|
/* §NEW 播放模式 / 播放队列自检:歌单滑窗队列(6 首)+ 播放态 */
|
||||||
|
mock_playlists(); mock_songs();
|
||||||
|
pthread_mutex_lock(&g_play.mu);
|
||||||
|
g_play.state = NCM_PC_PLAYING;
|
||||||
|
g_play.cur_song = 0;
|
||||||
|
g_play.play_mode = NCM_MODE_LISTLOOP;
|
||||||
|
g_play.play_song = g_st.songs[0];
|
||||||
|
g_play.play_song_valid = 1;
|
||||||
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
} else if (strcmp(name, "plsplay") == 0 || strcmp(name, "homeplay") == 0 ||
|
} else if (strcmp(name, "plsplay") == 0 || strcmp(name, "homeplay") == 0 ||
|
||||||
strcmp(name, "barhit") == 0) {
|
strcmp(name, "barhit") == 0) {
|
||||||
/* mini 播放栏目检/命中自检:非播放页(歌单/首页)+ 播放态 → 底部显示 mini 栏 */
|
/* mini 播放栏目检/命中自检:非播放页(歌单/首页)+ 播放态 → 底部显示 mini 栏 */
|
||||||
@@ -254,7 +266,8 @@ int main(int argc, char **argv)
|
|||||||
else if (strcmp(name, "home") == 0) v = home_view_create();
|
else if (strcmp(name, "home") == 0) v = home_view_create();
|
||||||
else if (strcmp(name, "playlist") == 0 || strcmp(name, "plsplay") == 0 ||
|
else if (strcmp(name, "playlist") == 0 || strcmp(name, "plsplay") == 0 ||
|
||||||
strcmp(name, "barhit") == 0) v = playlist_view_create();
|
strcmp(name, "barhit") == 0) v = playlist_view_create();
|
||||||
else if (page_id(name) == ACT_PLAYER) v = player_view_create(); /* player/lands/playerbuf/playererr */
|
else if (page_id(name) == ACT_PLAYER) v = player_view_create(); /* player/lands/playerbuf/playererr/modetest */
|
||||||
|
else if (page_id(name) == ACT_QUEUE) v = queue_view_create(); /* §NEW 播放队列页 */
|
||||||
else if (strcmp(name, "settings") == 0) v = settings_view_create();
|
else if (strcmp(name, "settings") == 0) v = settings_view_create();
|
||||||
else if (strcmp(name, "search") == 0 || strcmp(name, "searchhist") == 0 ||
|
else if (strcmp(name, "search") == 0 || strcmp(name, "searchhist") == 0 ||
|
||||||
strcmp(name, "searchpl") == 0 || strcmp(name, "searchtap") == 0) v = search_view_create();
|
strcmp(name, "searchpl") == 0 || strcmp(name, "searchtap") == 0) v = search_view_create();
|
||||||
@@ -358,6 +371,78 @@ int main(int argc, char **argv)
|
|||||||
if (!ok_tg) return 1;
|
if (!ok_tg) return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(name, "modetest") == 0) {
|
||||||
|
/* §NEW 播放模式三态逻辑自检:列表循环(顺序回绕)/单曲循环(自然播完重播)/随机(下一首随机+上一首回溯)。 */
|
||||||
|
int ok = 1;
|
||||||
|
pthread_mutex_lock(&g_play.mu);
|
||||||
|
g_play.cur_song = 0; g_play.state = NCM_PC_PLAYING; g_play.play_mode = NCM_MODE_LISTLOOP;
|
||||||
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
|
int cnt = appstate_queue_count();
|
||||||
|
player_manager_next(1); int c1 = player_manager_playing_song(); /* 0→1 */
|
||||||
|
player_manager_next(-1); int c2 = player_manager_playing_song(); /* 1→0 */
|
||||||
|
player_manager_next(-1); int c3 = player_manager_playing_song(); /* 0→5 回绕 */
|
||||||
|
player_manager_next(1); int c4 = player_manager_playing_song(); /* 5→0 回绕 */
|
||||||
|
ok &= (cnt == 6 && c1 == 1 && c2 == 0 && c3 == 5 && c4 == 0);
|
||||||
|
/* 单曲循环:自然播完 → 重播本曲(cur 不变,state=BUFFER) */
|
||||||
|
player_manager_set_play_mode(NCM_MODE_SINGLE);
|
||||||
|
player_manager_next_intent();
|
||||||
|
int cs = player_manager_playing_song();
|
||||||
|
int st = player_manager_state();
|
||||||
|
ok &= (cs == 0 && st == NCM_PC_BUFFER);
|
||||||
|
/* 单曲循环手上一首:顺序回绕(0→5) */
|
||||||
|
player_manager_next(-1);
|
||||||
|
int c5 = player_manager_playing_song();
|
||||||
|
ok &= (c5 == 5);
|
||||||
|
/* 随机:next 变(≠5),prev 历史回溯回 5 */
|
||||||
|
player_manager_set_play_mode(NCM_MODE_SHUFFLE);
|
||||||
|
player_manager_next(1); int c6 = player_manager_playing_song();
|
||||||
|
player_manager_next(-1); int c7 = player_manager_playing_song();
|
||||||
|
ok &= (c6 >= 0 && c6 < 6 && c6 != 5 && c7 == 5);
|
||||||
|
/* 模式循环:LISTLOOP→SINGLE→SHUFFLE→LISTLOOP */
|
||||||
|
player_manager_set_play_mode(NCM_MODE_LISTLOOP);
|
||||||
|
int m1 = (NCM_MODE_LISTLOOP + 1) % NCM_MODE_COUNT;
|
||||||
|
int m2 = (m1 + 1) % NCM_MODE_COUNT;
|
||||||
|
int m3 = (m2 + 1) % NCM_MODE_COUNT;
|
||||||
|
ok &= (m1 == NCM_MODE_SINGLE && m2 == NCM_MODE_SHUFFLE && m3 == NCM_MODE_LISTLOOP);
|
||||||
|
printf("modetest: listloop=%d single=%d shuffle=%d cycle=%d -> %s\n",
|
||||||
|
(c1 == 1 && c2 == 0 && c3 == 5 && c4 == 0),
|
||||||
|
(cs == 0 && st == NCM_PC_BUFFER && c5 == 5),
|
||||||
|
(c6 != 5 && c7 == 5),
|
||||||
|
(m1 == NCM_MODE_SINGLE && m2 == NCM_MODE_SHUFFLE && m3 == NCM_MODE_LISTLOOP),
|
||||||
|
ok ? "PASS" : "FAIL");
|
||||||
|
if (!ok) return 1;
|
||||||
|
}
|
||||||
|
if (strcmp(name, "queuetest") == 0) {
|
||||||
|
/* §NEW 播放队列:点播切歌保持当前 queue_src(歌单/搜索两源)+ 队列长度一致。 */
|
||||||
|
pthread_mutex_lock(&g_play.mu);
|
||||||
|
g_play.cur_song = 0; g_play.state = NCM_PC_PLAYING; g_play.play_mode = NCM_MODE_LISTLOOP;
|
||||||
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
|
int cnt0 = appstate_queue_count();
|
||||||
|
player_play_queue_song(2); /* PLAYLIST 队列点播第 2 首 */
|
||||||
|
int cur = player_manager_playing_song();
|
||||||
|
int qsrc = appstate_get_queue();
|
||||||
|
int ok1 = (cnt0 == 6 && cur == 2 && qsrc == NCM_QUEUE_PLAYLIST);
|
||||||
|
/* 切到 SEARCH 队列:点播第 1 首,保持 SEARCH */
|
||||||
|
static const char *sn[] = { "S1", "S2", "S3" };
|
||||||
|
appstate_set_queue(NCM_QUEUE_SEARCH);
|
||||||
|
g_st.search_song_count = 3;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
snprintf(g_st.search_songs[i].id, sizeof g_st.search_songs[i].id, "%d", 2000 + i);
|
||||||
|
snprintf(g_st.search_songs[i].name, sizeof g_st.search_songs[i].name, "%s", sn[i]);
|
||||||
|
snprintf(g_st.search_songs[i].art_name, sizeof g_st.search_songs[i].art_name, "A%d", i);
|
||||||
|
snprintf(g_st.search_songs[i].album_name, sizeof g_st.search_songs[i].album_name, "Al%d", i);
|
||||||
|
g_st.search_songs[i].duration_ms = 100000;
|
||||||
|
}
|
||||||
|
int cnt1 = appstate_queue_count();
|
||||||
|
player_play_queue_song(1);
|
||||||
|
int cur2 = player_manager_playing_song();
|
||||||
|
int qsrc2 = appstate_get_queue();
|
||||||
|
int ok2 = (cnt1 == 3 && cur2 == 1 && qsrc2 == NCM_QUEUE_SEARCH);
|
||||||
|
printf("queuetest: playlist=%s search=%s -> %s\n", ok1 ? "PASS" : "FAIL",
|
||||||
|
ok2 ? "PASS" : "FAIL", (ok1 && ok2) ? "PASS" : "FAIL");
|
||||||
|
if (!(ok1 && ok2)) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
meow_ui_shutdown();
|
meow_ui_shutdown();
|
||||||
printf("dumped %s -> %s\n", name, dump);
|
printf("dumped %s -> %s\n", name, dump);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -89,7 +89,11 @@ static ncm_rect_t g_cover_r; /* 封面页静态带(方形大图
|
|||||||
static ncm_rect_t g_dyn_r; /* 封面页动态带(元信息/进度/控制/指示点) */
|
static ncm_rect_t g_dyn_r; /* 封面页动态带(元信息/进度/控制/指示点) */
|
||||||
static int g_title_y, g_artist_y, g_qual_y, g_times_y;
|
static int g_title_y, g_artist_y, g_qual_y, g_times_y;
|
||||||
static ncm_rect_t g_bar;
|
static ncm_rect_t g_bar;
|
||||||
static int g_ctl_y, g_prev_x, g_play_x, g_next_x, g_r_side, g_r_play, g_dots_y;
|
static int g_ctl_y, g_prev_x, g_play_x, g_next_x, g_mode_x, g_queue_x, g_r_side, g_r_play, g_dots_y;
|
||||||
|
/* §NEW 模式/队列钮:三键左(mode,prev 更左)/ 三键右(queue,next 更右)。间距 60 保不遮挡。 */
|
||||||
|
#define GAP_SIDE 60
|
||||||
|
#define MODE_BTN_R (g_r_side) /* 与侧钮同半径(命中取 CTL_RR) */
|
||||||
|
#define MODE_LBL_SZ 14 /* 模式/队列钮文字字号(两字,居中) */
|
||||||
|
|
||||||
/* ---- 歌词页几何 ---- */
|
/* ---- 歌词页几何 ---- */
|
||||||
static int g_lyr_cx; /* 歌词垂直中心(屏中线) */
|
static int g_lyr_cx; /* 歌词垂直中心(屏中线) */
|
||||||
@@ -284,6 +288,18 @@ static void draw_ctrl_btn(int icon, int cx, int cy, int r, uint32_t circle, uint
|
|||||||
draw_md3_icon(icon, cx, cy, (r * 4) / 3, fg);
|
draw_md3_icon(icon, cx, cy, (r * 4) / 3, fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文字钮:圆形底 + 居中两字文案(§NEW 模式/队列钮)。无逐帧三角行扫,纯圆形填充。 */
|
||||||
|
static void draw_text_btn(const char *label, int cx, int cy, int r, uint32_t circle, uint32_t fg)
|
||||||
|
{
|
||||||
|
for (int dy = -r; dy <= r; dy++) {
|
||||||
|
int half = (int)sqrt((double)(r * r - dy * dy));
|
||||||
|
if (half <= 0) continue;
|
||||||
|
meow_ui_fill_rect(cx - half, cy + dy, 2 * half, 1, circle);
|
||||||
|
}
|
||||||
|
if (label && label[0])
|
||||||
|
meow_ui_text_center(cx, cy - MODE_LBL_SZ / 2, label, MODE_LBL_SZ, fg);
|
||||||
|
}
|
||||||
|
|
||||||
/* 圆角封面四角裁掉:把方形图四角(圆角外-方形内)盖回背景色(廉价圆角,无 GPU 裁剪)。 */
|
/* 圆角封面四角裁掉:把方形图四角(圆角外-方形内)盖回背景色(廉价圆角,无 GPU 裁剪)。 */
|
||||||
static void round_corners_bg(int x, int y, int w, int h, int r, uint32_t bg)
|
static void round_corners_bg(int x, int y, int w, int h, int r, uint32_t bg)
|
||||||
{
|
{
|
||||||
@@ -335,6 +351,8 @@ static void player_compute_geom(void)
|
|||||||
g_times_y = g_bar.y + g_bar.h + 12; /* 500 */
|
g_times_y = g_bar.y + g_bar.h + 12; /* 500 */
|
||||||
g_ctl_y = g_times_y + 64; /* 564 */
|
g_ctl_y = g_times_y + 64; /* 564 */
|
||||||
g_prev_x = W / 2 - 92; g_play_x = W / 2; g_next_x = W / 2 + 92;
|
g_prev_x = W / 2 - 92; g_play_x = W / 2; g_next_x = W / 2 + 92;
|
||||||
|
g_mode_x = g_prev_x - GAP_SIDE; /* 播放模式三态钮(prev 更左) */
|
||||||
|
g_queue_x = g_next_x + GAP_SIDE; /* 当前播放队列钮(next 更右) */
|
||||||
g_r_side = 24; g_r_play = 34;
|
g_r_side = 24; g_r_play = 34;
|
||||||
g_dots_y = g_ctl_y + 62; /* 626 */
|
g_dots_y = g_ctl_y + 62; /* 626 */
|
||||||
|
|
||||||
@@ -449,6 +467,12 @@ static void plr_draw_cover_dyn(void *ctx)
|
|||||||
g_play_x, g_ctl_y, g_r_play, pa->red, pa->on_red);
|
g_play_x, g_ctl_y, g_r_play, pa->red, pa->on_red);
|
||||||
draw_ctrl_btn(NCM_I_NEXT, g_next_x, g_ctl_y, g_r_side, PLR_CTRL_BG, PLR_CTRL_FG);
|
draw_ctrl_btn(NCM_I_NEXT, g_next_x, g_ctl_y, g_r_side, PLR_CTRL_BG, PLR_CTRL_FG);
|
||||||
|
|
||||||
|
/* §NEW 播放模式三态钮(三键左,prev 更左)+ 当前播放队列钮(三键右,next 更右):
|
||||||
|
* 文案反映当前模式(随机/列表/单曲);队列钮进入实时播放列表。 */
|
||||||
|
draw_text_btn(ncm_mode_name(player_manager_play_mode()), g_mode_x, g_ctl_y,
|
||||||
|
MODE_BTN_R, PLR_CTRL_BG, PLR_CTRL_FG);
|
||||||
|
draw_text_btn("\u961f\u5217", g_queue_x, g_ctl_y, MODE_BTN_R, PLR_CTRL_BG, PLR_CTRL_FG); /* 队列 */
|
||||||
|
|
||||||
/* 页面指示点 */
|
/* 页面指示点 */
|
||||||
draw_page_dots(g_dots_y);
|
draw_page_dots(g_dots_y);
|
||||||
}
|
}
|
||||||
@@ -604,7 +628,9 @@ void player_touch_down(int x, int y)
|
|||||||
interactive = 1;
|
interactive = 1;
|
||||||
if (in_circle(g_prev_x, g_ctl_y, CTL_RR, x, y) ||
|
if (in_circle(g_prev_x, g_ctl_y, CTL_RR, x, y) ||
|
||||||
in_circle(g_play_x, g_ctl_y, g_r_play + 6, x, y) ||
|
in_circle(g_play_x, g_ctl_y, g_r_play + 6, x, y) ||
|
||||||
in_circle(g_next_x, g_ctl_y, CTL_RR, x, y))
|
in_circle(g_next_x, g_ctl_y, CTL_RR, x, y) ||
|
||||||
|
in_circle(g_mode_x, g_ctl_y, CTL_RR, x, y) ||
|
||||||
|
in_circle(g_queue_x, g_ctl_y, CTL_RR, x, y))
|
||||||
interactive = 1;
|
interactive = 1;
|
||||||
}
|
}
|
||||||
g_touch_on = (x > PLR_EDGE_W) && (y >= meow_ui_statusbar_hotzone()) && !interactive;
|
g_touch_on = (x > PLR_EDGE_W) && (y >= meow_ui_statusbar_hotzone()) && !interactive;
|
||||||
@@ -675,6 +701,19 @@ void player_on_tap(int x, int y)
|
|||||||
}
|
}
|
||||||
if (in_circle(g_prev_x, g_ctl_y, CTL_RR, x, y)) { player_manager_next(-1); return; }
|
if (in_circle(g_prev_x, g_ctl_y, CTL_RR, x, y)) { player_manager_next(-1); return; }
|
||||||
if (in_circle(g_next_x, g_ctl_y, CTL_RR, x, y)) { player_manager_next(1); return; }
|
if (in_circle(g_next_x, g_ctl_y, CTL_RR, x, y)) { player_manager_next(1); return; }
|
||||||
|
|
||||||
|
/* §NEW 播放模式三态钮:点击循环切换(SHUFFLE→LISTLOOP→SINGLE→SHUFFLE)+ 持久化 */
|
||||||
|
if (in_circle(g_mode_x, g_ctl_y, CTL_RR, x, y)) {
|
||||||
|
int m = player_manager_play_mode();
|
||||||
|
int nxt = (m + 1) % NCM_MODE_COUNT;
|
||||||
|
player_manager_set_play_mode(nxt);
|
||||||
|
appstate_apply_play_mode(nxt); /* 记住模式(退出后保留) */
|
||||||
|
player_dyn_invalidate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* §NEW 当前播放队列钮:进实时播放列表页 */
|
||||||
|
if (in_circle(g_queue_x, g_ctl_y, CTL_RR, x, y)) { queue_open(); return; }
|
||||||
|
|
||||||
if (y >= g_bar.y - 10 && y <= g_bar.y + g_bar.h + 10 && x >= g_bar.x && x <= g_bar.x + g_bar.w) {
|
if (y >= g_bar.y - 10 && y <= g_bar.y + g_bar.h + 10 && x >= g_bar.x && x <= g_bar.x + g_bar.w) {
|
||||||
int64_t dur = player_manager_dur_ms();
|
int64_t dur = player_manager_dur_ms();
|
||||||
if (dur > 0) player_seek((int64_t)(x - g_bar.x) * dur / g_bar.w);
|
if (dur > 0) player_seek((int64_t)(x - g_bar.x) * dur / g_bar.w);
|
||||||
@@ -731,6 +770,28 @@ void player_play_search_song(int i)
|
|||||||
meow_view_page_push(ACT_PLAYER);
|
meow_view_page_push(ACT_PLAYER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 从「当前实时播放队列」第 idx 首播放(§NEW 队列页点播):保持当前 queue_src 不变
|
||||||
|
* (不把队列切回歌单/不覆写歌单窗口)——上/下一首仍在该队列内循环。封面用窗口/搜索数组
|
||||||
|
* 内的元数据(不触发网络),占位行由播放线程物化后快照接管。 */
|
||||||
|
void player_play_queue_song(int idx)
|
||||||
|
{
|
||||||
|
int cnt = appstate_queue_count();
|
||||||
|
if (idx < 0 || idx >= cnt) return;
|
||||||
|
ncm_song_t s;
|
||||||
|
int have = 0;
|
||||||
|
if (appstate_get_queue() == NCM_QUEUE_SEARCH) {
|
||||||
|
if (appstate_search_song(idx, &s) == 0) have = 1;
|
||||||
|
} else if (appstate_song_copy(idx, &s) == 0) {
|
||||||
|
have = 1;
|
||||||
|
}
|
||||||
|
const char *cover = have ? (s.al_pic[0] ? s.al_pic : g_st.cover_url) : g_st.cover_url;
|
||||||
|
player_manager_play(idx);
|
||||||
|
appstate_set_playing_song(idx, NULL);
|
||||||
|
if (have) cover_request(cover, s.id);
|
||||||
|
g_ppage = 0;
|
||||||
|
meow_view_page_push(ACT_PLAYER);
|
||||||
|
}
|
||||||
|
|
||||||
void player_pause_toggle(int on)
|
void player_pause_toggle(int on)
|
||||||
{
|
{
|
||||||
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
|
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
|
||||||
|
|||||||
@@ -0,0 +1,377 @@
|
|||||||
|
/* ui/queue.c — 当前实时播放队列页(ACT_QUEUE,§NEW:播放页「队列」钮进入)。
|
||||||
|
*
|
||||||
|
* 展示**当前播放队列**(player_manager 的 queue_src —— 歌单滑动窗口 或 搜索单曲结果集),
|
||||||
|
* 含当前曲高亮、可点某行切到那首播放。数据源与分页复用歌单滑动窗口(REPORT §17):
|
||||||
|
* - count = appstate_queue_count()(全量:track_ids_count 或 search_song_count,可 5194 首);
|
||||||
|
* - 行内容 = src2 逐行按 queue_src 取(PLAYLIST→滑窗拷贝,SEARCH→搜索数组),未物化槽显示
|
||||||
|
* ".../加载中" 占位,视口 ±24 行未物化 → 后台 worker ensure_range 补载(盘缓存优先,批≤NCM_SONG_BATCH);
|
||||||
|
* - 列表本身由 meow_listview 虚拟化(只画视口内行),不整歌单常驻内存 → 任意长度不卡死。
|
||||||
|
* - SEARCH 队列行全在内存(≤256),无需 ensure;PLAYLIST 队列走滑窗+ensure(含网络)。
|
||||||
|
* 当前曲高亮 = 播放页三态钮旁队列页以红色左条 + 红色轻染标记 player_manager_playing_song()
|
||||||
|
* 所在行(queue idx 与播放队列下标一致,两种 queue_src 通用)。
|
||||||
|
*
|
||||||
|
* 布局(无封面头块,纯列表):topbar「播放列表」+ 曲目列表 + 底部 `first-last / N 首` 页脚。
|
||||||
|
* 左缘返回 = 弹回上一页(播放页,app.c);点击行 → player_play_queue_song(保持当前 queue_src)。
|
||||||
|
*/
|
||||||
|
#include "../ncm_app_internal.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
typedef struct { meow_view *view; int active; } queue_ctrl;
|
||||||
|
static queue_ctrl g_qc;
|
||||||
|
|
||||||
|
static meow_widget *g_tb;
|
||||||
|
static meow_widget *g_list;
|
||||||
|
static ncm_rect_t r_cont, r_list;
|
||||||
|
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
||||||
|
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||||
|
static int g_last_count, g_last_cur;
|
||||||
|
|
||||||
|
/* 队列补载(PLAYLIST)后台 worker 参数:目标区间 [lo,hi)(全局下标)。SEARCH 无需补载。 */
|
||||||
|
static pthread_mutex_t g_qmu = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static pthread_t g_qthr;
|
||||||
|
static int g_qthr_on;
|
||||||
|
static volatile int g_ens_lo, g_ens_hi;
|
||||||
|
static volatile int g_ens_err;
|
||||||
|
static volatile int g_ens_done; /* worker 已完成的代次(tick 消费) */
|
||||||
|
static volatile int g_ens_acked;
|
||||||
|
|
||||||
|
static void q_drag_reset(void)
|
||||||
|
{
|
||||||
|
g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 队列第 idx 行的 ncm_song_t:按 queue_src 取。返回 0=已物化(out 有效);-1=未物化/占位。
|
||||||
|
* PLAYLIST:滑窗拷贝(UI 线程不触发网络);SEARCH:搜索数组(内存)。 */
|
||||||
|
static int q_song_get(int idx, ncm_song_t *out)
|
||||||
|
{
|
||||||
|
if (appstate_get_queue() == NCM_QUEUE_SEARCH)
|
||||||
|
return (appstate_search_song(idx, out) == 0) ? 0 : -1;
|
||||||
|
return (appstate_song_copy(idx, out) == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表数据源(meow_list2_source):逐行按需填充(不做 IO,§5.5)。未物化槽显示占位行。 */
|
||||||
|
static int q_src2(int idx, meow_list_item *out, void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
int cnt = appstate_queue_count();
|
||||||
|
if (idx < 0 || idx >= cnt) return 0;
|
||||||
|
static ncm_song_t s_s;
|
||||||
|
static char sub[256];
|
||||||
|
if (q_song_get(idx, &s_s) != 0) {
|
||||||
|
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||||
|
out->title = "...";
|
||||||
|
out->subtitle = "加载中";
|
||||||
|
out->right = MEOW_LIST_RIGHT_NONE;
|
||||||
|
out->on = 0; out->enabled = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (s_s.art_name[0] && s_s.album_name[0]) snprintf(sub, sizeof sub, "%s / %s", s_s.art_name, s_s.album_name);
|
||||||
|
else if (s_s.art_name[0]) snprintf(sub, sizeof sub, "%s", s_s.art_name);
|
||||||
|
else if (s_s.album_name[0]) snprintf(sub, sizeof sub, "%s", s_s.album_name);
|
||||||
|
else snprintf(sub, sizeof sub, "-");
|
||||||
|
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||||
|
out->title = s_s.name;
|
||||||
|
out->subtitle = sub;
|
||||||
|
out->right = MEOW_LIST_RIGHT_NONE;
|
||||||
|
out->on = 0; out->enabled = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行背景(z=5,画在 listview 文本之下):每行 surfaceContainer 圆角卡 + 「当前播放行」红色左条/轻染。 */
|
||||||
|
static void q_row_bg(void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
if (meow_ui_activity_active() != ACT_QUEUE) return;
|
||||||
|
int cnt = appstate_queue_count();
|
||||||
|
if (cnt <= 0) return;
|
||||||
|
const meow_theme *th = meow_theme_get();
|
||||||
|
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||||
|
int item = APP_ROW_H;
|
||||||
|
int off = meow_listview_scroll(g_list);
|
||||||
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
|
if (last > cnt) last = cnt;
|
||||||
|
int cur = player_manager_playing_song();
|
||||||
|
for (int i = first; i < last; i++) {
|
||||||
|
int ry = r_list.y + (i * item - off) + 1, th2 = item - 2;
|
||||||
|
if (ry < r_list.y || ry + th2 > r_list.y + r_list.h) continue; /* 只画完整落在视口内的行 */
|
||||||
|
if (i == cur) {
|
||||||
|
/* 当前播放行:红色左条 + 红色轻染(高亮),底用 red_ctr 浅红 */
|
||||||
|
ncm_fill_round(r_list.x, ry, r_list.w, th2, 12, pa->red_ctr);
|
||||||
|
meow_ui_fill_rect(r_list.x + 2, ry + 4, 4, th2 - 8, pa->red);
|
||||||
|
} else {
|
||||||
|
ncm_fill_round(r_list.x, ry, r_list.w, th2, 12, th->surfaceContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主绘制(z=60):当前行红色 mini-vinyl 缩略座标(文本由 listview z=10 画)。 */
|
||||||
|
static void q_draw(void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
if (meow_ui_activity_active() != ACT_QUEUE) return;
|
||||||
|
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||||
|
int cnt = appstate_queue_count();
|
||||||
|
if (cnt <= 0) return;
|
||||||
|
int item = APP_ROW_H, isz = 24;
|
||||||
|
int off = meow_listview_scroll(g_list);
|
||||||
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
|
if (last > cnt) last = cnt;
|
||||||
|
for (int i = first; i < last; i++) {
|
||||||
|
int ry = r_list.y + (i * item - off) + 1, th2 = item - 2;
|
||||||
|
if (ry < r_list.y || ry + th2 > r_list.y + r_list.h) continue;
|
||||||
|
int icy = ry + (item - isz) / 2, bx = r_list.x + 16;
|
||||||
|
ncm_fill_round(bx, icy, isz, isz, 6, pa->red);
|
||||||
|
meow_ui_fill_rect(bx + isz / 2 - 2, icy + isz / 2 - 2, 4, 4, pa->on_red);
|
||||||
|
if (g_press_row == i) {
|
||||||
|
meow_ui_fill_rect(r_list.x, ry, 4, item - 2, pa->red);
|
||||||
|
meow_ui_fill_rect_alpha(r_list.x, ry, r_list.w, item - 2, pa->red, 26);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void q_draw_empty(void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
if (meow_ui_activity_active() != ACT_QUEUE) return;
|
||||||
|
if (appstate_queue_count() != 0) return;
|
||||||
|
ncm_draw_empty(r_list, "暂无播放队列", "播放一首歌曲后在此查看列表");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void q_draw_footer(void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
if (meow_ui_activity_active() != ACT_QUEUE) return;
|
||||||
|
int total = appstate_queue_count();
|
||||||
|
if (total <= 0) return;
|
||||||
|
int first = meow_listview_scroll(g_list) / APP_ROW_H + 1;
|
||||||
|
int last = first + r_list.h / APP_ROW_H;
|
||||||
|
if (last > total) last = total;
|
||||||
|
char foot[96];
|
||||||
|
snprintf(foot, sizeof foot, "%d-%d / %d 首", first, last, total);
|
||||||
|
ncm_draw_footer(r_cont, foot);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void q_apply(void)
|
||||||
|
{
|
||||||
|
if (g_list) {
|
||||||
|
int cnt = appstate_queue_count();
|
||||||
|
meow_listview_set_count(g_list, cnt);
|
||||||
|
if (cnt != g_last_count) { g_last_count = cnt; meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); }
|
||||||
|
}
|
||||||
|
const meow_theme *th = meow_theme_get();
|
||||||
|
meow_ui_set_page_bg(th->bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *queue_worker(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
int lo, hi;
|
||||||
|
pthread_mutex_lock(&g_qmu);
|
||||||
|
lo = g_ens_lo; hi = g_ens_hi;
|
||||||
|
pthread_mutex_unlock(&g_qmu);
|
||||||
|
int rc = 0;
|
||||||
|
if (!g_host_mock) {
|
||||||
|
/* §26:滑窗补载(盘缓存优先,批网格补载);非致命(部分失败留占位行) */
|
||||||
|
rc = (lo < hi) ? appstate_song_ensure_range(lo, hi) : NCM_OK;
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&g_qmu);
|
||||||
|
g_ens_err = rc;
|
||||||
|
g_ens_done++;
|
||||||
|
pthread_mutex_unlock(&g_qmu);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 视口驱动滑窗补载(仅 PLAYLIST 队列):视口 ±24 行有未物化槽 → 发 ensure worker。
|
||||||
|
* SEARCH 队列全在内存,无需补载。 */
|
||||||
|
static void q_maybe_ensure(void)
|
||||||
|
{
|
||||||
|
if (appstate_get_queue() != NCM_QUEUE_PLAYLIST) return;
|
||||||
|
if (g_qthr_on || g_host_mock) return;
|
||||||
|
int total = appstate_queue_count();
|
||||||
|
if (total <= 0) return;
|
||||||
|
int scroll = meow_listview_scroll(g_list);
|
||||||
|
int first = scroll / APP_ROW_H;
|
||||||
|
int last = (scroll + r_list.h) / APP_ROW_H + 1;
|
||||||
|
int lo = first - 24, hi = last + 24;
|
||||||
|
if (lo < 0) lo = 0;
|
||||||
|
if (hi > total) hi = total;
|
||||||
|
if (lo >= hi) return;
|
||||||
|
if (appstate_range_loaded(lo, hi)) return; /* 已物化:无操作 */
|
||||||
|
pthread_mutex_lock(&g_qmu);
|
||||||
|
g_ens_lo = lo; g_ens_hi = hi;
|
||||||
|
pthread_mutex_unlock(&g_qmu);
|
||||||
|
g_qthr_on = 1;
|
||||||
|
if (ncm_thread_create(&g_qthr, queue_worker, NULL) != 0) {
|
||||||
|
g_qthr_on = 0; ncm_snack("加载失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void queue_open(void)
|
||||||
|
{
|
||||||
|
/* 从播放页「队列」钮进入:push 本页后按当前 queue 刷新(首屏立刻有 count/行)。 */
|
||||||
|
meow_view_page_push(ACT_QUEUE);
|
||||||
|
q_apply();
|
||||||
|
meow_ui_paint_full();
|
||||||
|
q_maybe_ensure();
|
||||||
|
}
|
||||||
|
|
||||||
|
int queue_is_active(void) { return g_qc.active; }
|
||||||
|
|
||||||
|
int queue_on_tap(int x, int y)
|
||||||
|
{
|
||||||
|
if (g_tb) {
|
||||||
|
int ah = meow_topappbar_hit(g_tb, x, y);
|
||||||
|
if (ah == MEOW_TOPBAR_BACK) { q_drag_reset(); meow_view_page_pop(); return 1; }
|
||||||
|
}
|
||||||
|
if (meow_widget_hit(g_list, x, y)) {
|
||||||
|
int was_drag = (g_press_delta > 10);
|
||||||
|
int row = (g_press_row >= 0) ? g_press_row : meow_listview_hit(g_list, x, y);
|
||||||
|
q_drag_reset();
|
||||||
|
if (row < 0 || was_drag) return 1;
|
||||||
|
player_play_queue_song(row); /* 点播:保持当前 queue_src,占位行由播放线程物化 */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void queue_touch_down(int x, int y)
|
||||||
|
{
|
||||||
|
q_drag_reset();
|
||||||
|
if (appstate_queue_count() <= 0) return;
|
||||||
|
if (meow_widget_hit(g_list, x, y)) {
|
||||||
|
int row = meow_listview_hit(g_list, x, y);
|
||||||
|
if (row >= 0) g_press_row = row;
|
||||||
|
g_dragging = 1; g_drag_last_y = y;
|
||||||
|
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void queue_touch_move(int x, int y)
|
||||||
|
{
|
||||||
|
(void)x;
|
||||||
|
if (!g_dragging) return;
|
||||||
|
if (g_press_row >= 0) {
|
||||||
|
int d = y - g_drag_last_y; if (d < 0) d = -d;
|
||||||
|
g_press_delta += d;
|
||||||
|
if (g_press_delta > 10) g_press_row = -1;
|
||||||
|
}
|
||||||
|
int dy = y - g_drag_last_y; g_drag_last_y = y;
|
||||||
|
if (dy == 0) return;
|
||||||
|
int before = meow_listview_scroll(g_list);
|
||||||
|
meow_listview_scroll_by(g_list, -dy);
|
||||||
|
if (meow_listview_scroll(g_list) != before) {
|
||||||
|
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||||
|
q_maybe_ensure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void queue_swipe(int dir, int dist)
|
||||||
|
{
|
||||||
|
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
|
||||||
|
if (appstate_queue_count() <= 0) return;
|
||||||
|
int del = (dir == MEOW_SWIPE_UP) ? dist : -dist;
|
||||||
|
int before = meow_listview_scroll(g_list);
|
||||||
|
meow_listview_scroll_by(g_list, del);
|
||||||
|
if (meow_listview_scroll(g_list) != before) {
|
||||||
|
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||||
|
q_maybe_ensure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void queue_tick(uint64_t now)
|
||||||
|
{
|
||||||
|
(void)now;
|
||||||
|
chrome_tick();
|
||||||
|
if (!g_qc.active) return;
|
||||||
|
/* worker 补载完成 → 刷新列表(占位行自愈) */
|
||||||
|
if (g_qthr_on) {
|
||||||
|
int done, acked;
|
||||||
|
pthread_mutex_lock(&g_qmu);
|
||||||
|
done = g_ens_done; acked = g_ens_acked;
|
||||||
|
pthread_mutex_unlock(&g_qmu);
|
||||||
|
if (done != acked) {
|
||||||
|
pthread_mutex_lock(&g_qmu);
|
||||||
|
g_ens_acked = done;
|
||||||
|
pthread_mutex_unlock(&g_qmu);
|
||||||
|
g_qthr_on = 0;
|
||||||
|
if (g_ens_err != 0) ncm_snack("加载失败");
|
||||||
|
q_apply();
|
||||||
|
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 当前播放曲变化(切歌/顺延)→ 重画高亮行 */
|
||||||
|
int cur = player_manager_playing_song();
|
||||||
|
if (cur != g_last_cur) {
|
||||||
|
g_last_cur = cur;
|
||||||
|
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void q_page_create(meow_view *v, void *ctx)
|
||||||
|
{
|
||||||
|
(void)v;
|
||||||
|
((queue_ctrl *)ctx)->view = v;
|
||||||
|
r_cont = ncm_content_rect();
|
||||||
|
r_list = NR(16, r_cont.y + 8, r_cont.w - 32,
|
||||||
|
r_cont.y + r_cont.h - APP_FOOTER_H - (r_cont.y + 8));
|
||||||
|
if (r_list.h < 0) r_list.h = 0;
|
||||||
|
meow_ui_activity_push(ACT_QUEUE);
|
||||||
|
g_tb = ncm_topbar_create("播放列表", 1, NULL);
|
||||||
|
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
||||||
|
q_src2, NULL, MEOW_LIST_SEL_NONE);
|
||||||
|
meow_listview_set_item_h(g_list, APP_ROW_H);
|
||||||
|
meow_ui_activity_pop();
|
||||||
|
meow_widget_set_z(g_list, 10);
|
||||||
|
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||||
|
q_row_bg, NULL);
|
||||||
|
g_draw_id = meow_draw_custom_register(60, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
|
||||||
|
q_draw, NULL);
|
||||||
|
g_empty_id = meow_draw_custom_register(1, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||||
|
q_draw_empty, NULL);
|
||||||
|
g_footer_id = meow_draw_custom_register(1, r_cont.x, r_cont.y + r_cont.h - APP_FOOTER_H,
|
||||||
|
r_cont.w, APP_FOOTER_H, q_draw_footer, NULL);
|
||||||
|
q_drag_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void q_page_enter(meow_view *v, void *ctx)
|
||||||
|
{
|
||||||
|
(void)v;
|
||||||
|
((queue_ctrl *)ctx)->active = 1;
|
||||||
|
const meow_theme *th = meow_theme_get();
|
||||||
|
meow_ui_set_page_bg(th->bg);
|
||||||
|
g_last_count = -1; g_last_cur = -2;
|
||||||
|
q_apply();
|
||||||
|
meow_ui_paint_full();
|
||||||
|
q_maybe_ensure();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void q_page_leave(meow_view *v, void *ctx)
|
||||||
|
{
|
||||||
|
(void)v;
|
||||||
|
((queue_ctrl *)ctx)->active = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void q_page_destroy(meow_view *v, void *ctx)
|
||||||
|
{
|
||||||
|
(void)v; (void)ctx;
|
||||||
|
if (g_bg_id) { meow_draw_unregister(g_bg_id); g_bg_id = 0; }
|
||||||
|
if (g_draw_id) { meow_draw_unregister(g_draw_id); g_draw_id = 0; }
|
||||||
|
if (g_empty_id) { meow_draw_unregister(g_empty_id); g_empty_id = 0; }
|
||||||
|
if (g_footer_id){ meow_draw_unregister(g_footer_id);g_footer_id = 0; }
|
||||||
|
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
|
||||||
|
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
|
||||||
|
}
|
||||||
|
|
||||||
|
meow_view *queue_view_create(void)
|
||||||
|
{
|
||||||
|
static const meow_view_hooks h = {
|
||||||
|
.on_create = q_page_create,
|
||||||
|
.on_enter = q_page_enter,
|
||||||
|
.on_leave = q_page_leave,
|
||||||
|
.on_destroy = q_page_destroy,
|
||||||
|
};
|
||||||
|
return meow_view_create(ACT_QUEUE, MEOW_VIEW_ACTIVITY, &h, &g_qc);
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ CORE="core/ncm_common.c core/ncm_json.c core/ncm_store.c core/ncm_http.c \
|
|||||||
core/ncm_crypto.c core/ncm_req.c core/ncm_login.c core/ncm_api.c \
|
core/ncm_crypto.c core/ncm_req.c core/ncm_login.c core/ncm_api.c \
|
||||||
core/ncm_cache.c core/ncm_ctx.c core/ncm_apicache.c"
|
core/ncm_cache.c core/ncm_ctx.c core/ncm_apicache.c"
|
||||||
APP="app/app.c app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \
|
APP="app/app.c app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \
|
||||||
app/ui/settings.c app/ui/search.c app/ui/minibar.c app/ui/ncm_colors.c app/ui/chrome.c \
|
app/ui/settings.c app/ui/search.c app/ui/minibar.c app/ui/queue.c app/ui/ncm_colors.c app/ui/chrome.c \
|
||||||
app/logic/appstate.c app/logic/lyric.c app/logic/songmeta.c \
|
app/logic/appstate.c app/logic/lyric.c app/logic/songmeta.c \
|
||||||
app/logic/cover.c app/logic/account.c app/play/player_manager.c app/play/audio_out.c \
|
app/logic/cover.c app/logic/account.c app/play/player_manager.c app/play/audio_out.c \
|
||||||
app/play/ipc_media.c app/play/decoder.c app/play/stream_src.c app/play/minimp3_impl.c \
|
app/play/ipc_media.c app/play/decoder.c app/play/stream_src.c app/play/minimp3_impl.c \
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ $CC_HOST -O2 -c -I"$SDK_DIR" -I"$ROOT/patch_v2/meow_launcherd" "$SDK_DIR/test_ho
|
|||||||
|
|
||||||
echo "==> host app sources (exclude app.c: has main + g_st; test provides them)"
|
echo "==> host app sources (exclude app.c: has main + g_st; test provides them)"
|
||||||
APP_SRC="app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \
|
APP_SRC="app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \
|
||||||
app/ui/settings.c app/ui/search.c app/ui/minibar.c app/ui/ncm_colors.c app/ui/chrome.c \
|
app/ui/settings.c app/ui/search.c app/ui/minibar.c app/ui/queue.c app/ui/ncm_colors.c app/ui/chrome.c \
|
||||||
app/logic/appstate.c app/logic/lyric.c app/logic/songmeta.c \
|
app/logic/appstate.c app/logic/lyric.c app/logic/songmeta.c \
|
||||||
app/logic/cover.c app/logic/account.c app/play/player_manager.c app/play/audio_out.c \
|
app/logic/cover.c app/logic/account.c app/play/player_manager.c app/play/audio_out.c \
|
||||||
app/play/ipc_media.c app/play/decoder.c app/play/stream_src.c app/play/minimp3_impl.c"
|
app/play/ipc_media.c app/play/decoder.c app/play/stream_src.c app/play/minimp3_impl.c"
|
||||||
|
|||||||
Reference in new issue
Block a user