HiFi player project, first commit.

This commit is contained in:
francis.wang committed 2026-10-26 20:07:12 +08:00
commit 4126ea13a1
49 files changed
+6333

No files matched your search

+323
View File
@@ -0,0 +1,323 @@
/**
****************************************************************************************************
* @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;
}
}