ncm 1e331964 (text 764KB); 0 warning; 宿主离屏6页截图目检+真实QR可扫+qemu-mipsel干净启动+解码446xRT 真机待联调: ALSA时序/803扫码闭环/媒体会话联动/gbk大字库渲染
301 lines
11 KiB
C
301 lines
11 KiB
C
/* ui/player.c — 播放页(ACT_PLAYER)。
|
|
*
|
|
* 布局:封面(后台 cover 线程解码) + 标题/歌手 + 进度条(可拖 seek) + 播放/暂停/上下曲 + 滚动歌词
|
|
* (时间轴解析,当前行高亮居中)。播放页横竖屏可切(device LCD 为竖屏 480x720,故为**布局**切换:
|
|
* 竖屏=封面在上、歌词在下;横屏=封面在左、信息+歌词在右,仍在 480x720 画布内)。
|
|
* 进度/状态每 tick 读 play_manager(跨线程 mu 保护),歌词行由 pos_ms 推出。
|
|
* 全部内容画进一个 custom 绘制物;控件钮/进度条/歌词命中用 player_draw 计算的共享几何。
|
|
*/
|
|
#include "../ncm_app_internal.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
typedef struct { meow_view *view; int active; } player_ctrl;
|
|
static player_ctrl g_plc;
|
|
|
|
static meow_widget *g_tb;
|
|
static ncm_rect_t r_cont;
|
|
static int g_custom_id;
|
|
static int g_landscape; /* 0=竖屏 1=横屏(布局) */
|
|
static int g_last_pos_ms, g_last_state, g_last_line;
|
|
|
|
/* 共享几何(player_draw 计算 → player_on_tap 命中用) */
|
|
static ncm_rect_t g_cov; /* 封面 */
|
|
static int g_bar_x, g_bar_y, g_bar_w;
|
|
static int g_cprev_x, g_cplay_x, g_cnext_x, g_cby, g_cr;
|
|
static int g_lyr_x, g_lyr_y, g_lyr_w, g_lyr_h; /* 歌词区 */
|
|
#define CTL_RR 28 /* 侧钮命中半径 */
|
|
|
|
static int cur_song(void)
|
|
{
|
|
int c = player_manager_playing_song();
|
|
return (c >= 0 && c < g_st.song_count) ? c : -1;
|
|
}
|
|
|
|
static void fmt_ms(int64_t ms, char *out, size_t sz)
|
|
{
|
|
if (ms < 0) ms = 0;
|
|
int sec = (int)(ms / 1000);
|
|
snprintf(out, sz, "%d:%02d", sec / 60, sec % 60);
|
|
}
|
|
|
|
/* 实心三角近似:right=向右指(顶点在右),left=向左指(顶点在左)。 */
|
|
static void tri(int x0, int y0, int h, int w, int dir, uint32_t color)
|
|
{
|
|
for (int i = 0; i < h; i++) {
|
|
int wi = 1 + i * w / h;
|
|
if (dir == 1) meow_ui_fill_rect(x0, y0 + i, wi, 1, color);
|
|
else meow_ui_fill_rect(x0 - wi + 1, y0 + i, wi, 1, color);
|
|
}
|
|
}
|
|
|
|
/* 圆形按钮 + 图标。kind: 0=|<<(prev) 1=>play 2=||pause 3=>>|(next)。 */
|
|
static void draw_ctl(int cx, int cy, int r, int kind, uint32_t base, uint32_t fg)
|
|
{
|
|
for (int i = 0; i < 2 * r; i++) {
|
|
int dy = i - r;
|
|
int half = (int)sqrt((double)(r * r - dy * dy));
|
|
if (half <= 0) continue;
|
|
meow_ui_fill_rect(cx - half, cy - r + i, 2 * half, 1, base);
|
|
}
|
|
int t = r / 2;
|
|
if (kind == 0) { /* |<< */
|
|
meow_ui_fill_rect(cx - r - 2, cy - t, 4, 2*t, fg);
|
|
tri(cx + r - 4, cy - t, 2*t, 2*t, -1, fg);
|
|
} else if (kind == 1) { /* > */
|
|
tri(cx - t, cy - t, 2*t, 2*t, 1, fg);
|
|
} else if (kind == 2) { /* || */
|
|
meow_ui_fill_rect(cx - t - 2, cy - t, 4, 2*t, fg);
|
|
meow_ui_fill_rect(cx + t - 2, cy - t, 4, 2*t, fg);
|
|
} else { /* >>| */
|
|
tri(cx - r + 4, cy - t, 2*t, 2*t, 1, fg);
|
|
meow_ui_fill_rect(cx + r - 2, cy - t, 4, 2*t, fg);
|
|
}
|
|
}
|
|
|
|
static void player_draw(void *ctx)
|
|
{
|
|
(void)ctx;
|
|
if (meow_ui_activity_active() != ACT_PLAYER) return;
|
|
const meow_theme *th = meow_theme_get();
|
|
int cx = r_cont.x + r_cont.w / 2;
|
|
int sc = cur_song();
|
|
const ncm_song_t *s = (sc >= 0) ? &g_st.songs[sc] : NULL;
|
|
|
|
/* 封面区域 */
|
|
int covsz = 220;
|
|
if (g_landscape) g_cov = NR(r_cont.x + 24, r_cont.y + 60, covsz, covsz);
|
|
else g_cov = NR(cx - covsz/2, r_cont.y + 10, covsz, covsz);
|
|
|
|
meow_ui_fill_rect(g_cov.x, g_cov.y, g_cov.w, g_cov.h, th->surfaceContainerHigh);
|
|
meow_ui_draw_rect(g_cov.x, g_cov.y, g_cov.w, g_cov.h, th->outlineVariant);
|
|
meow_img *cov = cover_get();
|
|
if (cov) meow_ui_img_cover(g_cov.x, g_cov.y, covsz, cov);
|
|
else meow_ui_text_center(g_cov.x + g_cov.w/2, g_cov.y + g_cov.h/2 - 10, "no artwork", 20, th->dim);
|
|
|
|
int info_x = (g_landscape) ? g_cov.x + g_cov.w + 28 : cx;
|
|
int info_w = (g_landscape) ? (r_cont.x + r_cont.w - 16 - info_x) : r_cont.w;
|
|
|
|
int ty = (g_landscape) ? g_cov.y + 8 : g_cov.y + g_cov.h + 8;
|
|
meow_ui_text_center(info_x, ty, s ? s->name : "nothing playing", 28, th->fg);
|
|
meow_ui_text_center(info_x, ty + 34, s ? s->art_name : "", 20, th->dim);
|
|
|
|
/* 进度条 + 时间 */
|
|
g_bar_x = (g_landscape) ? info_x - info_w/2 : 32;
|
|
g_bar_w = (g_landscape) ? info_w - 24 : r_cont.w - 64;
|
|
g_bar_y = ty + 78;
|
|
int pos = (int)player_manager_pos_ms();
|
|
int dur = (int)player_manager_dur_ms();
|
|
if (dur <= 0 && s) dur = (int)(s->duration_ms);
|
|
int frac = dur > 0 ? (int)((int64_t)pos * 1000 / dur) : 0;
|
|
if (frac < 0) frac = 0; if (frac > 1000) frac = 1000;
|
|
|
|
char lp[16], rp[16];
|
|
fmt_ms(pos, lp, sizeof lp);
|
|
fmt_ms(dur, rp, sizeof rp);
|
|
meow_ui_text(g_bar_x, g_bar_y - 20, lp, 20, th->dim);
|
|
meow_ui_text_right(g_bar_x + g_bar_w, g_bar_y - 20, rp, 20, th->dim);
|
|
meow_ui_fill_rect(g_bar_x, g_bar_y, g_bar_w, 4, th->surfaceVariant);
|
|
meow_ui_fill_rect(g_bar_x, g_bar_y, (int)((int64_t)g_bar_w * frac / 1000), 4, th->primary);
|
|
|
|
/* 控制钮 */
|
|
g_cby = g_bar_y + 40;
|
|
int cw = 56, gap = (g_landscape) ? 34 : 26;
|
|
g_cr = cw/2 + 4;
|
|
g_cprev_x = info_x - cw - gap/2 - 4;
|
|
g_cplay_x = info_x;
|
|
g_cnext_x = info_x + cw + gap/2 + 4;
|
|
draw_ctl(g_cprev_x, g_cby, cw/2, 0, th->secondaryContainer, th->onSecondaryContainer);
|
|
int st = player_manager_state();
|
|
draw_ctl(g_cplay_x, g_cby, g_cr, (st == NCM_PC_PAUSED) ? 1 : 2, th->primary, th->onPrimary);
|
|
draw_ctl(g_cnext_x, g_cby, cw/2, 3, th->secondaryContainer, th->onSecondaryContainer);
|
|
|
|
/* 歌词区 */
|
|
if (g_landscape) {
|
|
g_lyr_x = r_cont.x + 24;
|
|
g_lyr_y = g_cov.y + g_cov.h + 12;
|
|
g_lyr_w = r_cont.w - 48;
|
|
g_lyr_h = r_cont.y + r_cont.h - g_lyr_y;
|
|
} else {
|
|
g_lyr_x = r_cont.x;
|
|
g_lyr_y = g_cby + g_cr + 10;
|
|
g_lyr_w = r_cont.w;
|
|
g_lyr_h = r_cont.y + r_cont.h - g_lyr_y;
|
|
}
|
|
if (g_lyr_h < 0) g_lyr_h = 0;
|
|
/* 背景(歌词区隔断) */
|
|
meow_ui_fill_rect(g_lyr_x, g_lyr_y, g_lyr_w, 1, th->surfaceVariant);
|
|
|
|
int n = lyric_count();
|
|
int line_h = 28;
|
|
int center_y = g_lyr_y + (g_lyr_h > line_h * 11 ? g_lyr_h / 2 : 140);
|
|
int lcx = g_lyr_x + g_lyr_w / 2;
|
|
if (n == 0) {
|
|
meow_ui_text_center(lcx, center_y - 10, "no lyric", 20, th->dim);
|
|
return;
|
|
}
|
|
int64_t ps = player_manager_pos_ms();
|
|
int cur2 = lyric_line_at(ps);
|
|
for (int i = cur2 - 6; i <= cur2 + 5; i++) {
|
|
if (i < 0 || i >= n) continue;
|
|
int yy = center_y + (i - cur2) * line_h;
|
|
int is_cur = (i == cur2);
|
|
meow_ui_text_center(lcx, yy, lyric_line_text(i), is_cur ? 24 : 20,
|
|
is_cur ? th->primary : th->dim);
|
|
const char *tl = lyric_line_tl(i);
|
|
if (is_cur && tl && tl[0])
|
|
meow_ui_text_center(lcx, yy + 24, tl, 20, th->secondary);
|
|
}
|
|
}
|
|
|
|
int player_is_active(void) { return g_plc.active; }
|
|
int player_screen(void) { return g_landscape; }
|
|
|
|
int player_set_screen(int landscape)
|
|
{
|
|
int old = g_landscape;
|
|
g_landscape = landscape ? 1 : 0;
|
|
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
|
|
return old;
|
|
}
|
|
|
|
void player_on_tap(int x, int y)
|
|
{
|
|
if (g_tb) {
|
|
int ah = meow_topappbar_hit(g_tb, x, y);
|
|
if (ah == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return; }
|
|
if (ah >= 0) { player_set_screen(!g_landscape); return; } /* ⟳ 布局切换 */
|
|
}
|
|
if (g_bar_w <= 0) return;
|
|
/* 控制钮 */
|
|
int r = g_cr;
|
|
if (x >= g_cplay_x - r && x <= g_cplay_x + r && y >= g_cby - r && y <= g_cby + r) {
|
|
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
|
|
else player_manager_pause(1);
|
|
return;
|
|
}
|
|
if (x >= g_cprev_x - CTL_RR && x <= g_cprev_x + CTL_RR &&
|
|
y >= g_cby - CTL_RR && y <= g_cby + CTL_RR) { player_manager_next(-1); return; }
|
|
if (x >= g_cnext_x - CTL_RR && x <= g_cnext_x + CTL_RR &&
|
|
y >= g_cby - CTL_RR && y <= g_cby + CTL_RR) { player_manager_next(1); return; }
|
|
/* 进度条 seek */
|
|
if (y >= g_bar_y - 8 && y <= g_bar_y + 12 && x >= g_bar_x && x <= g_bar_x + g_bar_w) {
|
|
int64_t dur = player_manager_dur_ms();
|
|
if (dur > 0) player_seek((int64_t)(x - g_bar_x) * dur / g_bar_w);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void player_play_song(int idx)
|
|
{
|
|
if (idx < 0 || idx >= g_st.song_count) return;
|
|
player_manager_play(idx);
|
|
appstate_set_playing_song(idx, g_st.cover_url);
|
|
cover_request(g_st.cover_url);
|
|
meow_view_page_push(ACT_PLAYER);
|
|
}
|
|
|
|
void player_pause_toggle(int on)
|
|
{
|
|
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
|
|
else player_manager_pause(1);
|
|
(void)on;
|
|
}
|
|
|
|
void player_next(int dir) { player_manager_next(dir); }
|
|
void player_seek(int64_t ms) { player_manager_seek(ms); }
|
|
void player_on_swipe(int dir, int dist) { (void)dir; (void)dist; }
|
|
|
|
void player_tick(uint64_t now)
|
|
{
|
|
(void)now;
|
|
chrome_tick();
|
|
if (!g_plc.active) return;
|
|
|
|
int pos = (int)player_manager_pos_ms();
|
|
int st = player_manager_state();
|
|
int line = (lyric_count() > 0) ? lyric_line_at(pos) : -1;
|
|
|
|
if (pos != g_last_pos_ms || st != g_last_state) {
|
|
g_last_pos_ms = pos;
|
|
g_last_state = st;
|
|
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
|
|
} else if (line != g_last_line) {
|
|
g_last_line = line;
|
|
meow_ui_invalidate(g_lyr_x, g_lyr_y, g_lyr_w, g_lyr_h);
|
|
}
|
|
if (cover_changed()) {
|
|
cover_ack();
|
|
meow_ui_invalidate(g_cov.x, g_cov.y, g_cov.w, g_cov.h);
|
|
}
|
|
}
|
|
|
|
static void plr_page_create(meow_view *v, void *ctx)
|
|
{
|
|
(void)v;
|
|
((player_ctrl *)ctx)->view = v;
|
|
r_cont = ncm_content_rect();
|
|
meow_ui_activity_push(ACT_PLAYER); /* owner=ACT_PLAYER */
|
|
g_tb = ncm_topbar_create("Now Playing", 1, "\xe2\x86\xbb"); /* 返回 + ⟳ 布局 */
|
|
meow_ui_activity_pop();
|
|
g_custom_id = meow_draw_custom_register(1, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
|
|
player_draw, NULL);
|
|
g_last_pos_ms = -1; g_last_state = -1; g_last_line = -1;
|
|
}
|
|
|
|
static void plr_page_enter(meow_view *v, void *ctx)
|
|
{
|
|
(void)v;
|
|
((player_ctrl *)ctx)->active = 1;
|
|
const meow_theme *th = meow_theme_get();
|
|
meow_ui_set_page_bg(th->bg);
|
|
meow_ui_paint_full();
|
|
if (!g_host_mock) player_manager_load_lyric();
|
|
}
|
|
|
|
static void plr_page_leave(meow_view *v, void *ctx)
|
|
{
|
|
(void)v;
|
|
((player_ctrl *)ctx)->active = 0;
|
|
}
|
|
|
|
static void plr_page_destroy(meow_view *v, void *ctx)
|
|
{
|
|
(void)v; (void)ctx;
|
|
if (g_custom_id) { meow_draw_unregister(g_custom_id); g_custom_id = 0; }
|
|
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
|
|
}
|
|
|
|
meow_view *player_view_create(void)
|
|
{
|
|
static const meow_view_hooks h = {
|
|
.on_create = plr_page_create,
|
|
.on_enter = plr_page_enter,
|
|
.on_leave = plr_page_leave,
|
|
.on_destroy = plr_page_destroy,
|
|
};
|
|
return meow_view_create(ACT_PLAYER, MEOW_VIEW_ACTIVITY, &h, &g_plc);
|
|
}
|