First commit
This commit is contained in:
commit
61c37de884
171 files changed
+9201
No files matched your search
@@ -0,0 +1,265 @@
|
||||
# Player - optional carousel screen (base id 1). Full-screen album art + transport.
|
||||
# Remove it: drop this file from your config's package "files:" list.
|
||||
# Volume control + the volume overlay stay in the core; this is only the now-playing SCREEN.
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- priority: 690
|
||||
then:
|
||||
- lambda: 'id(g_present)[1] = true;' # register after clock
|
||||
|
||||
globals:
|
||||
- id: g_last_pic
|
||||
type: std::string
|
||||
restore_value: no
|
||||
- id: g_pos # position in the track (s) - progress bar
|
||||
type: int
|
||||
restore_value: no
|
||||
initial_value: '0'
|
||||
- id: g_dur # track length (s)
|
||||
type: int
|
||||
restore_value: no
|
||||
initial_value: '0'
|
||||
|
||||
# Required by online_image (fetching album art over HTTP)
|
||||
http_request:
|
||||
timeout: 10s
|
||||
verify_ssl: false
|
||||
|
||||
# Album art fetched dynamically from HA (entity_picture is a signed URL)
|
||||
|
||||
online_image:
|
||||
- id: album_art
|
||||
url: "${ha_base_url}/"
|
||||
format: JPEG
|
||||
type: RGB565
|
||||
resize: 360x360
|
||||
# Album art FIX: after download you must EXPLICITLY refresh the LVGL widget (it won't redraw itself)
|
||||
on_download_finished:
|
||||
- lvgl.image.update:
|
||||
id: ui_album
|
||||
src: album_art
|
||||
on_error:
|
||||
- logger.log: "Failed to fetch album art"
|
||||
|
||||
sensor:
|
||||
- platform: homeassistant
|
||||
id: media_pos
|
||||
entity_id: ${player_entity}
|
||||
attribute: media_position
|
||||
on_value:
|
||||
- lambda: 'id(g_pos) = (int) x;'
|
||||
- platform: homeassistant
|
||||
id: media_dur
|
||||
entity_id: ${player_entity}
|
||||
attribute: media_duration
|
||||
on_value:
|
||||
- lambda: 'id(g_dur) = (int) x;'
|
||||
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
id: media_title
|
||||
entity_id: ${player_entity}
|
||||
attribute: media_title
|
||||
on_value:
|
||||
- lvgl.label.update:
|
||||
id: lbl_title
|
||||
text: !lambda 'return x.empty() ? std::string("-") : x;'
|
||||
- platform: homeassistant
|
||||
id: media_artist
|
||||
entity_id: ${player_entity}
|
||||
attribute: media_artist
|
||||
on_value:
|
||||
- lvgl.label.update:
|
||||
id: lbl_artist
|
||||
text: !lambda 'return x;'
|
||||
- platform: homeassistant
|
||||
id: media_pic
|
||||
entity_id: ${player_entity}
|
||||
attribute: entity_picture
|
||||
on_value:
|
||||
- lambda: |-
|
||||
if (x.empty()) return;
|
||||
std::string url = x.rfind("http", 0) == 0 ? x : std::string("${ha_base_url}") + x;
|
||||
if (url == id(g_last_pic)) return; // same album art -> don't reload (less flicker)
|
||||
id(g_last_pic) = url;
|
||||
id(album_art)->set_url(url.c_str());
|
||||
id(album_art)->update();
|
||||
# Player state -> only "playing" shows the player; pause/stop = player disappears (clock)
|
||||
- platform: homeassistant
|
||||
id: media_state
|
||||
entity_id: ${player_entity}
|
||||
on_value:
|
||||
- lvgl.label.update: # icon on the button: pause when playing, play when not
|
||||
id: lbl_play
|
||||
text: !lambda 'return id(media_state).state == "playing" ? std::string("") : std::string("");'
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(g_va_active) && !id(g_boot) && id(g_mode) == 0 && (id(g_base) == 0 || id(g_base) == 1);' # auto-switch clock<->player only when we're on the clock/player (and not during boot splash)
|
||||
then:
|
||||
# Only switch on an ACTUAL transition: home -> player when playback starts,
|
||||
# player -> home when it stops. (Avoids re-navigating on every media-state event,
|
||||
# incl. the state HA pushes at boot, which used to flip the home to Classic.)
|
||||
- if:
|
||||
condition: { lambda: 'return x == "playing" && id(g_base) == 0;' }
|
||||
then:
|
||||
- lvgl.page.show: { id: page_player, animation: move_left, time: 250ms }
|
||||
- lambda: 'id(g_base) = 1;'
|
||||
- if:
|
||||
condition: { lambda: 'return x != "playing" && id(g_base) == 1;' }
|
||||
then:
|
||||
# back to the home base, respecting the active watchface (Classic shows
|
||||
# page_home directly; other faces self-show via g_wf_req) - don't hardcode Classic
|
||||
- lambda: 'id(g_base) = 0; id(g_nav_anim) = 1;'
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_watchface) == 0;' }
|
||||
then:
|
||||
- lvgl.page.show: { id: page_home, animation: move_right, time: 250ms }
|
||||
else:
|
||||
- lambda: 'id(g_wf_req) = true;'
|
||||
|
||||
interval:
|
||||
# nav: show this screen when it is selected in the carousel
|
||||
- interval: 50ms
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_nav_req) && id(g_base)==1;' }
|
||||
then:
|
||||
- lambda: 'id(g_nav_req) = false;'
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==0;' }, then: [lvgl.page.show: { id: page_player, animation: move_left, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==1;' }, then: [lvgl.page.show: { id: page_player, animation: move_right, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==2;' }, then: [lvgl.page.show: { id: page_player, animation: move_top, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==3;' }, then: [lvgl.page.show: { id: page_player, animation: move_bottom, time: 250ms }] }
|
||||
# track progress (position / time / progress bar)
|
||||
- interval: 1s
|
||||
then:
|
||||
# Track progress: tick the position while playing + update time and bar
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(media_state).state == "playing" && id(g_dur) > 0 && id(g_pos) < id(g_dur);'
|
||||
then:
|
||||
- lambda: 'id(g_pos)++;'
|
||||
- lvgl.label.update:
|
||||
id: lbl_time
|
||||
text: !lambda |-
|
||||
int p = id(g_pos), d = id(g_dur);
|
||||
if (d <= 0) return std::string("");
|
||||
char b[20]; snprintf(b, sizeof(b), "%d:%02d | %d:%02d", p/60, p%60, d/60, d%60);
|
||||
return std::string(b);
|
||||
- lvgl.bar.update:
|
||||
id: prog_bar
|
||||
value: !lambda 'return id(g_dur) > 0 ? (int)(1000L * id(g_pos) / id(g_dur)) : 0;'
|
||||
|
||||
lvgl:
|
||||
pages:
|
||||
# --- Player page: full-screen album art + scrim + title/artist + volume arc ---
|
||||
- id: page_player
|
||||
bg_color: 0x000000
|
||||
widgets:
|
||||
# Album art covering the whole round screen (360x360; the bezel hides the corners)
|
||||
- image:
|
||||
id: ui_album
|
||||
align: CENTER
|
||||
src: album_art
|
||||
# Dim the whole thing under text and buttons (readability)
|
||||
- obj:
|
||||
align: CENTER
|
||||
width: 360
|
||||
height: 360
|
||||
radius: 0
|
||||
border_width: 0
|
||||
bg_color: 0x000000
|
||||
bg_opa: 42%
|
||||
# Title (scroll) + artist (clip) - in the upper part
|
||||
- label:
|
||||
id: lbl_title
|
||||
align: CENTER
|
||||
y: -72
|
||||
width: 250
|
||||
long_mode: scroll_circular
|
||||
text_align: CENTER
|
||||
text_font: font_med
|
||||
text_color: 0xFFFFFF
|
||||
text: "-"
|
||||
- label:
|
||||
id: lbl_artist
|
||||
align: CENTER
|
||||
y: -46
|
||||
width: 230
|
||||
long_mode: clip
|
||||
text_align: CENTER
|
||||
text_font: font_artist
|
||||
text_color: 0x9FC7BE
|
||||
text: ""
|
||||
# Transport: prev / play-pause / next (touch buttons)
|
||||
- button:
|
||||
id: btn_prev
|
||||
align: CENTER
|
||||
x: -84
|
||||
y: 6
|
||||
width: 50
|
||||
height: 50
|
||||
radius: 25
|
||||
bg_color: 0x223230
|
||||
bg_opa: COVER
|
||||
border_width: 0
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: media_player.media_previous_track
|
||||
data: { entity_id: "${player_entity}" }
|
||||
widgets:
|
||||
- label: { align: CENTER, text: "", text_font: montserrat_22, text_color: 0xCFEAE4 }
|
||||
- button:
|
||||
id: btn_play
|
||||
align: CENTER
|
||||
x: 0
|
||||
y: 6
|
||||
width: 74
|
||||
height: 74
|
||||
radius: 37
|
||||
bg_color: 0x2FE6CF
|
||||
bg_opa: COVER
|
||||
border_width: 0
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: media_player.media_play_pause
|
||||
data: { entity_id: "${player_entity}" }
|
||||
widgets:
|
||||
- label: { id: lbl_play, align: CENTER, text: "", text_font: montserrat_28, text_color: 0x06201C }
|
||||
- button:
|
||||
id: btn_next
|
||||
align: CENTER
|
||||
x: 84
|
||||
y: 6
|
||||
width: 50
|
||||
height: 50
|
||||
radius: 25
|
||||
bg_color: 0x223230
|
||||
bg_opa: COVER
|
||||
border_width: 0
|
||||
on_click:
|
||||
- homeassistant.action:
|
||||
action: media_player.media_next_track
|
||||
data: { entity_id: "${player_entity}" }
|
||||
widgets:
|
||||
- label: { align: CENTER, text: "", text_font: montserrat_22, text_color: 0xCFEAE4 }
|
||||
# Time + progress bar
|
||||
- label:
|
||||
id: lbl_time
|
||||
align: CENTER
|
||||
y: 64
|
||||
text: ""
|
||||
text_font: font_artist
|
||||
text_color: 0xCFEAE4
|
||||
- bar:
|
||||
id: prog_bar
|
||||
align: CENTER
|
||||
y: 90
|
||||
width: 210
|
||||
height: 5
|
||||
min_value: 0
|
||||
max_value: 1000
|
||||
value: 0
|
||||
border_width: 0
|
||||
indicator:
|
||||
bg_color: 0x2FE6CF
|
||||
Reference in new issue
Block a user