text/plain
•
11.92 KB
•
363 lines
#include "now_playing_screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../audio_player.h"
#include "../moon.h"
#include "../playlist.h"
#include "../topbar.h"
static void format_time(uint32_t ms, char* buf, size_t buf_size) {
uint32_t total_sec = ms / 1000;
uint32_t hours = total_sec / 3600;
uint32_t mins = (total_sec % 3600) / 60;
uint32_t secs = total_sec % 60;
if (hours > 0) {
snprintf(buf, buf_size, "%u:%02u:%02u", (unsigned)hours, (unsigned)mins,
(unsigned)secs);
} else {
snprintf(buf, buf_size, "%u:%02u", (unsigned)mins, (unsigned)secs);
}
}
static void update_chapter_label(now_playing_screen_state_t* state) {
int current, total;
if (state->chapter_label && playlist_get_chapter_info(¤t, &total)) {
snprintf(string_buffer, sizeof(string_buffer), "%d / %d", current,
total);
lv_label_set_text(state->chapter_label, string_buffer);
}
}
static void update_playlist_label(now_playing_screen_state_t* state) {
if (state->playlist_label && playlist_count() > 1) {
snprintf(string_buffer, sizeof(string_buffer), "%d/%d",
playlist.index + 1, playlist.count);
lv_label_set_text(state->playlist_label, string_buffer);
}
}
static void create_chapter_label(now_playing_screen_state_t* state) {
int current, total;
if (playlist_get_chapter_info(¤t, &total)) {
state->chapter_label = lv_label_create(state->container);
lv_obj_set_style_text_color(state->chapter_label,
lv_color_hex(0xAAAAAA), 0);
// Move chapter label after title
lv_obj_move_to_index(state->chapter_label, 1);
update_chapter_label(state);
}
}
static void cancel_event_cb(lv_event_t* e) {
(void)e;
playlist_entry_t* entry = playlist_current();
if (entry && entry->type == PLAYLIST_AUDIOBOOK) {
playlist_save_bookmarks();
}
// Don't stop playback — let it continue in the background
navigate_back(SCREEN_HOME);
}
static void play_btn_cb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
return;
}
audio_state_t astate = audio_player_get_state();
if (astate == AUDIO_STATE_PLAYING) {
playlist_entry_t* entry = playlist_current();
if (entry && entry->type == PLAYLIST_AUDIOBOOK) {
playlist_save_bookmarks();
}
audio_player_pause();
} else if (astate == AUDIO_STATE_PAUSED) {
audio_player_resume();
}
}
static void prev_btn_cb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
return;
}
now_playing_screen_state_t* state = ui_state.now_playing;
if (!state) {
return;
}
playlist_play_prev();
// Refresh UI
const char* title = audio_player_get_title();
lv_label_set_text(state->title_label, title ? title : "Unknown");
update_playlist_label(state);
// Recreate chapter label if needed
if (state->chapter_label) {
lv_obj_delete(state->chapter_label);
state->chapter_label = NULL;
}
create_chapter_label(state);
}
static void next_btn_cb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
return;
}
now_playing_screen_state_t* state = ui_state.now_playing;
if (!state) {
return;
}
playlist_play_next();
// Refresh UI
const char* title = audio_player_get_title();
lv_label_set_text(state->title_label, title ? title : "Unknown");
update_playlist_label(state);
// Recreate chapter label if needed
if (state->chapter_label) {
lv_obj_delete(state->chapter_label);
state->chapter_label = NULL;
}
create_chapter_label(state);
}
ui_state_t setup_now_playing_screen(lv_obj_t* parent) {
now_playing_screen_state_t* state =
calloc(1, sizeof(now_playing_screen_state_t));
topbar_set_title("Now Playing");
// Main container
state->container = lv_obj_create(parent);
lv_obj_set_pos(state->container, 0, TOPBAR_H);
lv_obj_set_size(state->container, SCREEN_WIDTH, SCREEN_HEIGHT - TOPBAR_H);
lv_obj_set_flex_flow(state->container, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(state->container, LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(state->container, 4, 0);
lv_obj_set_style_pad_gap(state->container, 6, 0);
lv_obj_set_style_radius(state->container, 0, 0);
lv_obj_set_style_border_side(state->container, 0, 0);
lv_obj_clear_flag(state->container, LV_OBJ_FLAG_SCROLLABLE);
playlist_entry_t* entry = playlist_current();
if (!entry) {
// Nothing playing
state->title_label = lv_label_create(state->container);
lv_label_set_text(state->title_label, "Nothing playing");
lv_obj_set_width(state->title_label, LV_PCT(100));
// Just a back button
lv_obj_t* back_btn = lv_button_create(state->container);
lv_obj_set_size(back_btn, LV_SIZE_CONTENT, 24);
lv_obj_t* back_label = lv_label_create(back_btn);
lv_label_set_text(back_label, "Back");
lv_obj_center(back_label);
lv_obj_add_event_cb(back_btn, cancel_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(back_btn, cancel_event_cb, LV_EVENT_CANCEL, NULL);
lv_group_add_obj(lv_group_get_default(), back_btn);
lv_group_focus_obj(back_btn);
// Dummy widgets so update doesn't crash
state->progress_bar = lv_bar_create(state->container);
lv_obj_add_flag(state->progress_bar, LV_OBJ_FLAG_HIDDEN);
state->position_label = lv_label_create(state->container);
lv_obj_add_flag(state->position_label, LV_OBJ_FLAG_HIDDEN);
state->duration_label = lv_label_create(state->container);
lv_obj_add_flag(state->duration_label, LV_OBJ_FLAG_HIDDEN);
return (ui_state_t){.type = SCREEN_NOW_PLAYING, .now_playing = state};
}
// Title label (scrolling)
state->title_label = lv_label_create(state->container);
const char* title = audio_player_get_title();
lv_label_set_text(state->title_label, title ? title : "Unknown");
lv_label_set_long_mode(state->title_label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_width(state->title_label, LV_PCT(100));
// If audio is stopped, start playback
if (audio_player_get_state() == AUDIO_STATE_STOPPED) {
if (!playlist_start()) {
lv_label_set_text(state->title_label, "Playback failed");
} else {
// Update title after playback starts (metadata now available)
title = audio_player_get_title();
lv_label_set_text(state->title_label, title ? title : "Unknown");
}
create_chapter_label(state);
} else {
// Already playing — sync playlist state to match
playlist_sync_state();
create_chapter_label(state);
}
// Time row container
lv_obj_t* time_cont = lv_obj_create(state->container);
lv_obj_set_size(time_cont, SCREEN_WIDTH - 8, LV_SIZE_CONTENT);
lv_obj_set_flex_flow(time_cont, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(time_cont, LV_FLEX_ALIGN_SPACE_BETWEEN,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(time_cont, 0, 0);
lv_obj_set_style_border_width(time_cont, 0, 0);
lv_obj_set_style_bg_opa(time_cont, LV_OPA_TRANSP, 0);
state->position_label = lv_label_create(time_cont);
lv_label_set_text(state->position_label, "0:00");
lv_obj_set_style_text_color(state->position_label, lv_color_hex(0xAAAAAA),
0);
// Playlist position label (e.g. "1/3") in center of time row
if (playlist_count() > 1) {
state->playlist_label = lv_label_create(time_cont);
lv_obj_set_style_text_color(state->playlist_label,
lv_color_hex(0xAAAAAA), 0);
update_playlist_label(state);
}
state->duration_label = lv_label_create(time_cont);
lv_label_set_text(state->duration_label, "0:00");
lv_obj_set_style_text_color(state->duration_label, lv_color_hex(0xAAAAAA),
0);
// Progress bar
state->progress_bar = lv_bar_create(state->container);
lv_obj_set_size(state->progress_bar, SCREEN_WIDTH - 8, 6);
lv_bar_set_range(state->progress_bar, 0, 100);
lv_bar_set_value(state->progress_bar, 0, LV_ANIM_OFF);
lv_obj_set_style_bg_color(state->progress_bar, lv_color_hex(0x444444),
LV_PART_MAIN);
lv_obj_set_style_bg_color(state->progress_bar, lv_color_hex(0xE91E63),
LV_PART_INDICATOR);
// Button container
lv_obj_t* btn_cont = lv_obj_create(state->container);
lv_obj_set_size(btn_cont, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_flow(btn_cont, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(btn_cont, LV_FLEX_ALIGN_SPACE_EVENLY,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(btn_cont, 4, 0);
lv_obj_set_style_border_width(btn_cont, 0, 0);
lv_obj_set_style_bg_opa(btn_cont, LV_OPA_TRANSP, 0);
// Prev button
state->prev_btn = lv_button_create(btn_cont);
lv_obj_set_size(state->prev_btn, 24, 24);
lv_obj_t* prev_label = lv_label_create(state->prev_btn);
lv_label_set_text(prev_label, LV_SYMBOL_PREV);
lv_obj_center(prev_label);
lv_obj_add_event_cb(state->prev_btn, prev_btn_cb, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(state->prev_btn, cancel_event_cb, LV_EVENT_CANCEL,
NULL);
lv_group_add_obj(lv_group_get_default(), state->prev_btn);
// Play/Pause button
state->play_btn = lv_button_create(btn_cont);
lv_obj_set_size(state->play_btn, 24, 24);
lv_obj_t* play_label = lv_label_create(state->play_btn);
lv_label_set_text(play_label, LV_SYMBOL_PAUSE);
lv_obj_center(play_label);
lv_obj_add_event_cb(state->play_btn, play_btn_cb, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(state->play_btn, cancel_event_cb, LV_EVENT_CANCEL,
NULL);
lv_group_add_obj(lv_group_get_default(), state->play_btn);
// Next button
state->next_btn = lv_button_create(btn_cont);
lv_obj_set_size(state->next_btn, 24, 24);
lv_obj_t* next_label = lv_label_create(state->next_btn);
lv_label_set_text(next_label, LV_SYMBOL_NEXT);
lv_obj_center(next_label);
lv_obj_add_event_cb(state->next_btn, next_btn_cb, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(state->next_btn, cancel_event_cb, LV_EVENT_CANCEL,
NULL);
lv_group_add_obj(lv_group_get_default(), state->next_btn);
// Focus play button
lv_group_focus_obj(state->play_btn);
update_now_playing_screen(state);
return (ui_state_t){.type = SCREEN_NOW_PLAYING, .now_playing = state};
}
void free_now_playing_screen(now_playing_screen_state_t* state) {
if (!state) {
return;
}
free(state);
}
void update_now_playing_screen(now_playing_screen_state_t* state) {
if (!state) {
return;
}
playlist_entry_t* entry = playlist_current();
if (!entry) {
return;
}
// Apply pending bookmark seek
playlist_apply_bookmark();
// Detect playback completion and auto-advance
if (playlist_check_advance()) {
navigate_back(SCREEN_HOME);
return;
}
// Update title after chapter advance
audio_state_t audio_state = audio_player_get_state();
if (audio_state == AUDIO_STATE_PLAYING ||
audio_state == AUDIO_STATE_PAUSED) {
// Refresh chapter label
update_chapter_label(state);
}
// Update play/pause button icon
if (state->play_btn) {
lv_obj_t* play_label = lv_obj_get_child(state->play_btn, 0);
if (audio_state == AUDIO_STATE_PLAYING ||
audio_state == AUDIO_STATE_PAUSED) {
lv_label_set_text(play_label, audio_state == AUDIO_STATE_PLAYING
? LV_SYMBOL_PAUSE
: LV_SYMBOL_PLAY);
} else {
lv_label_set_text(play_label, LV_SYMBOL_PLAY);
}
}
// Update progress bar and time labels
uint32_t position_ms = audio_player_get_position_ms();
uint32_t duration_ms = audio_player_get_duration_ms();
// Before bookmark seek is applied, show the bookmark position
uint32_t bm_ms = playlist_get_bookmark_ms();
if (bm_ms > 0 && position_ms < bm_ms && playlist_is_bookmark_applied()) {
position_ms = bm_ms;
}
uint8_t progress = duration_ms > 0
? (uint8_t)((position_ms * 100) / duration_ms)
: audio_player_get_progress();
lv_bar_set_value(state->progress_bar, progress, LV_ANIM_OFF);
format_time(position_ms, string_buffer, sizeof(string_buffer));
lv_label_set_text(state->position_label, string_buffer);
if (duration_ms > 0) {
format_time(duration_ms, string_buffer, sizeof(string_buffer));
lv_label_set_text(state->duration_label, string_buffer);
} else {
lv_label_set_text(state->duration_label, "--:--");
}
}