/** **************************************************************************************************** * @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 #include #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; } }