324 lines
11 KiB
C
324 lines
11 KiB
C
/**
|
|
****************************************************************************************************
|
|
* @file ui_player.c
|
|
* @author HiFi Player Project
|
|
* @version V1.2
|
|
* @date 2026-01-01
|
|
* @brief 播放器界面
|
|
*
|
|
* ┌──────────────────────────┐
|
|
* │ ♪ HiFi PLAYER 01/12 │
|
|
* ├──────────────────────────┤
|
|
* │ ┌────┐ 歌名 │
|
|
* │ │ ♪ │ WAV 44100Hz 16bit │
|
|
* │ └────┘ ( PLAYING ) │
|
|
* │ 00:42 ────●────── 03:35 │
|
|
* │ 🔊 ──────●───── 60% │
|
|
* ├──────────────────────────┤
|
|
* │ [←K0] [⏯K1] [⏭K2] [🔊K3] │
|
|
* │ BACK PLAY NEXT VOL │
|
|
* └──────────────────────────┘
|
|
****************************************************************************************************
|
|
*/
|
|
|
|
#include "ui_player.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "ui_common.h"
|
|
#include "app.h"
|
|
#include "player.h"
|
|
|
|
/* ---------------- 控件 ---------------- */
|
|
static lv_obj_t *s_scr = NULL;
|
|
static lv_obj_t *s_lbl_index = NULL;
|
|
static lv_obj_t *s_lbl_name = NULL;
|
|
static lv_obj_t *s_lbl_format = NULL;
|
|
static lv_obj_t *s_lbl_chip = NULL;
|
|
static lv_obj_t *s_bar_prog = NULL;
|
|
static lv_obj_t *s_lbl_cur = NULL;
|
|
static lv_obj_t *s_lbl_tot = NULL;
|
|
static lv_obj_t *s_bar_vol = NULL;
|
|
static lv_obj_t *s_lbl_vol = NULL;
|
|
|
|
static ui_key_bar_t s_bar;
|
|
static ui_flash_t s_flash;
|
|
static uint32_t s_chip_color = 0xFFFFFFFF;
|
|
|
|
/* ---------------- 按键提示内容 ---------------- */
|
|
static const char *const s_symbols[APP_KEY_NUM] = {
|
|
LV_SYMBOL_LEFT, /* K0:返回主界面 */
|
|
LV_SYMBOL_PLAY, /* K1:播放 / 暂停(图标随状态切换) */
|
|
LV_SYMBOL_NEXT, /* K2:下一曲 */
|
|
LV_SYMBOL_VOLUME_MAX, /* K3:音量 */
|
|
};
|
|
|
|
static const char *const s_captions[APP_KEY_NUM] = {
|
|
"K0 BACK",
|
|
"K1 PLAY",
|
|
"K2 NEXT",
|
|
"K3 VOL",
|
|
};
|
|
|
|
static const bool s_enabled[APP_KEY_NUM] = { true, true, true, true };
|
|
|
|
/* ---------------- 状态显示 ---------------- */
|
|
|
|
static const char *status_text(player_status_t status, const char *message)
|
|
{
|
|
switch (status)
|
|
{
|
|
case PLAYER_STATUS_PLAYING:
|
|
return "PLAYING";
|
|
case PLAYER_STATUS_PAUSED:
|
|
return (message && message[0]) ? message : "PAUSED";
|
|
case PLAYER_STATUS_NO_SD:
|
|
return (message && message[0]) ? message : "NO SD CARD";
|
|
case PLAYER_STATUS_NO_MUSIC:
|
|
return (message && message[0]) ? message : "NO MUSIC";
|
|
case PLAYER_STATUS_ERROR:
|
|
return (message && message[0]) ? message : "ERROR";
|
|
case PLAYER_STATUS_BOOT:
|
|
default:
|
|
return (message && message[0]) ? message : "STARTING...";
|
|
}
|
|
}
|
|
|
|
static uint32_t status_color(player_status_t status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case PLAYER_STATUS_PLAYING:
|
|
return UI_COL_OK;
|
|
case PLAYER_STATUS_PAUSED:
|
|
return UI_COL_WARN;
|
|
case PLAYER_STATUS_NO_SD:
|
|
case PLAYER_STATUS_ERROR:
|
|
return UI_COL_ERR;
|
|
case PLAYER_STATUS_NO_MUSIC:
|
|
return UI_COL_WARN;
|
|
case PLAYER_STATUS_BOOT:
|
|
default:
|
|
return UI_COL_SUB;
|
|
}
|
|
}
|
|
|
|
/* ---------------- 创建界面 ---------------- */
|
|
|
|
void ui_player_create(void)
|
|
{
|
|
s_scr = lv_obj_create(NULL);
|
|
ui_style_screen(s_scr);
|
|
|
|
/* ---------------- 标题栏 ---------------- */
|
|
lv_obj_t *header = ui_card(s_scr, 0, 0, UI_HOR_RES, 30, UI_COL_PANEL, 0);
|
|
|
|
lv_obj_t *title = ui_label(header, LV_SYMBOL_AUDIO " HiFi PLAYER",
|
|
&lv_font_montserrat_16, UI_COL_ACCENT);
|
|
lv_obj_align(title, LV_ALIGN_LEFT_MID, 10, 0);
|
|
|
|
s_lbl_index = ui_label(header, "--/--", &lv_font_montserrat_14, UI_COL_SUB);
|
|
lv_obj_align(s_lbl_index, LV_ALIGN_RIGHT_MID, -10, 0);
|
|
|
|
/* ---------------- 封面卡片 ---------------- */
|
|
lv_obj_t *cover = ui_card(s_scr, 10, 36, 92, 92, 0x283A6B, 12);
|
|
|
|
lv_obj_set_style_bg_grad_color(cover, lv_color_hex(UI_COL_ACCENT2), LV_PART_MAIN);
|
|
lv_obj_set_style_bg_grad_dir(cover, LV_GRAD_DIR_VER, LV_PART_MAIN);
|
|
lv_obj_set_style_shadow_width(cover, 18, LV_PART_MAIN);
|
|
lv_obj_set_style_shadow_color(cover, lv_color_hex(UI_COL_ACCENT2), LV_PART_MAIN);
|
|
lv_obj_set_style_shadow_opa(cover, LV_OPA_30, LV_PART_MAIN);
|
|
|
|
lv_obj_t *note = ui_label(cover, LV_SYMBOL_AUDIO, &lv_font_montserrat_40, 0xFFFFFF);
|
|
lv_obj_center(note);
|
|
|
|
/* ---------------- 曲目信息 ---------------- */
|
|
s_lbl_name = ui_label(s_scr, "No media", &lv_font_montserrat_16, UI_COL_TEXT);
|
|
lv_obj_set_pos(s_lbl_name, 112, 36);
|
|
lv_obj_set_size(s_lbl_name, 198, 20);
|
|
lv_label_set_long_mode(s_lbl_name, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
|
|
|
s_lbl_format = ui_label(s_scr, "WAV / PCM", &lv_font_montserrat_12, UI_COL_SUB);
|
|
lv_obj_set_pos(s_lbl_format, 112, 60);
|
|
|
|
lv_obj_t *chip = ui_card(s_scr, 112, 82, LV_SIZE_CONTENT, 22, UI_COL_CARD, 11);
|
|
lv_obj_set_style_pad_hor(chip, 8, LV_PART_MAIN);
|
|
lv_obj_set_style_border_width(chip, 1, LV_PART_MAIN);
|
|
lv_obj_set_style_border_color(chip, lv_color_hex(UI_COL_TRACK), LV_PART_MAIN);
|
|
|
|
s_lbl_chip = ui_label(chip, "STARTING...", &lv_font_montserrat_12, UI_COL_SUB);
|
|
lv_obj_center(s_lbl_chip);
|
|
|
|
/* ---------------- 播放进度 ---------------- */
|
|
s_bar_prog = lv_bar_create(s_scr);
|
|
lv_obj_set_pos(s_bar_prog, 10, 130);
|
|
lv_obj_set_size(s_bar_prog, 300, 8);
|
|
lv_bar_set_range(s_bar_prog, 0, 100);
|
|
lv_bar_set_value(s_bar_prog, 0, LV_ANIM_OFF);
|
|
lv_obj_set_style_bg_color(s_bar_prog, lv_color_hex(UI_COL_TRACK), LV_PART_MAIN);
|
|
lv_obj_set_style_bg_opa(s_bar_prog, LV_OPA_COVER, LV_PART_MAIN);
|
|
lv_obj_set_style_radius(s_bar_prog, 4, LV_PART_MAIN);
|
|
lv_obj_set_style_bg_color(s_bar_prog, lv_color_hex(UI_COL_ACCENT), LV_PART_INDICATOR);
|
|
lv_obj_set_style_radius(s_bar_prog, 4, LV_PART_INDICATOR);
|
|
|
|
s_lbl_cur = ui_label(s_scr, "00:00", &lv_font_montserrat_12, UI_COL_SUB);
|
|
lv_obj_set_pos(s_lbl_cur, 10, 142);
|
|
|
|
s_lbl_tot = ui_label(s_scr, "--:--", &lv_font_montserrat_12, UI_COL_SUB);
|
|
lv_obj_align(s_lbl_tot, LV_ALIGN_TOP_RIGHT, -10, 142);
|
|
|
|
/* ---------------- 音量 ---------------- */
|
|
lv_obj_t *vol_icon = ui_label(s_scr, LV_SYMBOL_VOLUME_MAX, &lv_font_montserrat_14,
|
|
UI_COL_ACCENT);
|
|
lv_obj_set_pos(vol_icon, 10, 160);
|
|
|
|
s_bar_vol = lv_bar_create(s_scr);
|
|
lv_obj_set_pos(s_bar_vol, 32, 165);
|
|
lv_obj_set_size(s_bar_vol, 228, 6);
|
|
lv_bar_set_range(s_bar_vol, 0, 100);
|
|
lv_bar_set_value(s_bar_vol, 60, LV_ANIM_OFF);
|
|
lv_obj_set_style_bg_color(s_bar_vol, lv_color_hex(UI_COL_TRACK), LV_PART_MAIN);
|
|
lv_obj_set_style_bg_opa(s_bar_vol, LV_OPA_COVER, LV_PART_MAIN);
|
|
lv_obj_set_style_radius(s_bar_vol, 3, LV_PART_MAIN);
|
|
lv_obj_set_style_bg_color(s_bar_vol, lv_color_hex(UI_COL_ACCENT2), LV_PART_INDICATOR);
|
|
lv_obj_set_style_radius(s_bar_vol, 3, LV_PART_INDICATOR);
|
|
|
|
s_lbl_vol = ui_label(s_scr, "60%", &lv_font_montserrat_12, UI_COL_SUB);
|
|
lv_obj_align(s_lbl_vol, LV_ALIGN_TOP_RIGHT, -10, 158);
|
|
|
|
/* ---------------- 底部按键提示 ---------------- */
|
|
ui_key_bar_create(s_scr, &s_bar, s_symbols, s_captions, s_enabled);
|
|
}
|
|
|
|
/* ---------------- 显示 / 刷新 ---------------- */
|
|
|
|
void ui_player_show(void)
|
|
{
|
|
ui_key_bar_set_highlight(&s_bar, -1);
|
|
ui_player_tick();
|
|
lv_scr_load(s_scr);
|
|
}
|
|
|
|
void ui_player_tick(void)
|
|
{
|
|
player_state_t state;
|
|
char buffer[96];
|
|
|
|
player_get_state(&state);
|
|
|
|
/* ---- 标题栏:曲目序号 ---- */
|
|
if (state.track_total > 0)
|
|
{
|
|
snprintf(buffer, sizeof(buffer), "%02d/%02d",
|
|
(int)state.track_index + 1, (int)state.track_total);
|
|
}
|
|
else
|
|
{
|
|
snprintf(buffer, sizeof(buffer), "--/--");
|
|
}
|
|
ui_label_set_if_changed(s_lbl_index, buffer);
|
|
|
|
/* ---- 曲名 / 格式 / 状态 ---- */
|
|
ui_label_set_if_changed(s_lbl_name, state.track_name[0] ? state.track_name : "No media");
|
|
|
|
if (state.tot_sec > 0)
|
|
{
|
|
/* 位数相同只显示一个;源位深被提升时显示 "24->32bit" */
|
|
char bits_text[16];
|
|
uint8_t i2s_bits = state.i2s_bits ? state.i2s_bits : state.bits;
|
|
|
|
if (i2s_bits == state.bits)
|
|
{
|
|
snprintf(bits_text, sizeof(bits_text), "%ubit", (unsigned)state.bits);
|
|
}
|
|
else
|
|
{
|
|
snprintf(bits_text, sizeof(bits_text), "%u->%ubit",
|
|
(unsigned)state.bits, (unsigned)i2s_bits);
|
|
}
|
|
|
|
snprintf(buffer, sizeof(buffer), "%s %uHz %s %s",
|
|
state.codec[0] ? state.codec : "AUDIO",
|
|
(unsigned)state.samplerate, bits_text,
|
|
(state.channels == 1) ? "MONO" : "STEREO");
|
|
}
|
|
else
|
|
{
|
|
snprintf(buffer, sizeof(buffer), "%s / PCM",
|
|
state.codec[0] ? state.codec : "AUDIO");
|
|
}
|
|
ui_label_set_if_changed(s_lbl_format, buffer);
|
|
|
|
ui_label_set_if_changed(s_lbl_chip, status_text(state.status, state.message));
|
|
|
|
uint32_t color = status_color(state.status);
|
|
if (color != s_chip_color)
|
|
{
|
|
s_chip_color = color;
|
|
lv_obj_set_style_text_color(s_lbl_chip, lv_color_hex(color), LV_PART_MAIN);
|
|
}
|
|
|
|
/* ---- 播放进度 ---- */
|
|
uint32_t total = (state.tot_sec > 0) ? state.tot_sec : 1;
|
|
uint32_t current = (state.cur_sec > state.tot_sec) ? state.tot_sec : state.cur_sec;
|
|
|
|
lv_bar_set_value(s_bar_prog, (int32_t)((uint64_t)current * 100 / total), LV_ANIM_OFF);
|
|
|
|
ui_format_time(current, buffer, sizeof(buffer));
|
|
ui_label_set_if_changed(s_lbl_cur, buffer);
|
|
|
|
if (state.tot_sec > 0)
|
|
{
|
|
ui_format_time(state.tot_sec, buffer, sizeof(buffer));
|
|
}
|
|
else
|
|
{
|
|
snprintf(buffer, sizeof(buffer), "--:--");
|
|
}
|
|
ui_label_set_if_changed(s_lbl_tot, buffer);
|
|
|
|
/* ---- 音量 ---- */
|
|
lv_bar_set_value(s_bar_vol, (int32_t)state.volume, LV_ANIM_OFF);
|
|
snprintf(buffer, sizeof(buffer), "%u%%", (unsigned)state.volume);
|
|
ui_label_set_if_changed(s_lbl_vol, buffer);
|
|
|
|
/* ---- 播放键图标提示“按下后会发生什么” ---- */
|
|
ui_key_bar_set_icon(&s_bar, APP_KEY_1,
|
|
(state.status == PLAYER_STATUS_PLAYING) ? LV_SYMBOL_PAUSE
|
|
: LV_SYMBOL_PLAY);
|
|
|
|
/* ---- 按键高亮 ---- */
|
|
ui_key_bar_set_highlight(&s_bar, ui_flash_active(&s_flash));
|
|
}
|
|
|
|
/* ---------------- 按键 ---------------- */
|
|
|
|
void ui_player_flash(int key)
|
|
{
|
|
ui_flash_trigger(&s_flash, key);
|
|
}
|
|
|
|
void ui_player_handle_key(uint8_t key)
|
|
{
|
|
ui_flash_trigger(&s_flash, key);
|
|
|
|
switch (key)
|
|
{
|
|
case APP_KEY_1:
|
|
player_request(PLAYER_REQ_PLAY_PAUSE);
|
|
break;
|
|
|
|
case APP_KEY_2:
|
|
player_request(PLAYER_REQ_NEXT);
|
|
break;
|
|
|
|
case APP_KEY_3:
|
|
player_request(PLAYER_REQ_VOLUME);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|