mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-08 04:27:12 +03:00
artwork for Spotify
This commit is contained in:
@@ -52,7 +52,8 @@ static EXT_RAM_ATTR struct {
|
|||||||
bool visible;
|
bool visible;
|
||||||
} duration;
|
} duration;
|
||||||
struct {
|
struct {
|
||||||
bool enable, fit;
|
bool enable, active;
|
||||||
|
bool fit;
|
||||||
bool updated;
|
bool updated;
|
||||||
int tick;
|
int tick;
|
||||||
int offset;
|
int offset;
|
||||||
@@ -245,7 +246,7 @@ static void displayer_task(void *args) {
|
|||||||
GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
|
GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
|
||||||
|
|
||||||
// if we have not received artwork after 5s, display a default icon
|
// if we have not received artwork after 5s, display a default icon
|
||||||
if (displayer.artwork.enable && !displayer.artwork.updated && tick - displayer.artwork.tick > pdMS_TO_TICKS(5000)) {
|
if (displayer.artwork.active && !displayer.artwork.updated && tick - displayer.artwork.tick > pdMS_TO_TICKS(5000)) {
|
||||||
ESP_LOGI(TAG, "no artwork received, setting default");
|
ESP_LOGI(TAG, "no artwork received, setting default");
|
||||||
displayer_artwork((uint8_t*) default_artwork);
|
displayer_artwork((uint8_t*) default_artwork);
|
||||||
}
|
}
|
||||||
@@ -265,7 +266,7 @@ static void displayer_task(void *args) {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void displayer_artwork(uint8_t *data) {
|
void displayer_artwork(uint8_t *data) {
|
||||||
if (!displayer.artwork.enable) return;
|
if (!displayer.artwork.active) return;
|
||||||
|
|
||||||
int x = displayer.artwork.offset ? displayer.artwork.offset + ARTWORK_BORDER : 0;
|
int x = displayer.artwork.offset ? displayer.artwork.offset + ARTWORK_BORDER : 0;
|
||||||
int y = x ? 0 : 32;
|
int y = x ? 0 : 32;
|
||||||
@@ -420,7 +421,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
|
|||||||
switch(cmd) {
|
switch(cmd) {
|
||||||
case DISPLAYER_ACTIVATE: {
|
case DISPLAYER_ACTIVATE: {
|
||||||
char *header = va_arg(args, char*);
|
char *header = va_arg(args, char*);
|
||||||
bool artwork = va_arg(args, int);
|
displayer.artwork.active = displayer.artwork.enable && va_arg(args, int);
|
||||||
strncpy(displayer.header, header, HEADER_SIZE);
|
strncpy(displayer.header, header, HEADER_SIZE);
|
||||||
displayer.header[HEADER_SIZE] = '\0';
|
displayer.header[HEADER_SIZE] = '\0';
|
||||||
displayer.state = DISPLAYER_ACTIVE;
|
displayer.state = DISPLAYER_ACTIVE;
|
||||||
@@ -431,19 +432,20 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
|
|||||||
displayer.duration.visible = false;
|
displayer.duration.visible = false;
|
||||||
displayer.offset = displayer.boundary = 0;
|
displayer.offset = displayer.boundary = 0;
|
||||||
display_bus(&displayer, DISPLAY_BUS_TAKE);
|
display_bus(&displayer, DISPLAY_BUS_TAKE);
|
||||||
if (artwork) GDS_SetTextWidth(display, displayer.artwork.offset);
|
if (displayer.artwork.active) GDS_SetTextWidth(display, displayer.artwork.offset);
|
||||||
vTaskResume(displayer.task);
|
vTaskResume(displayer.task);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DISPLAYER_SUSPEND:
|
case DISPLAYER_SUSPEND:
|
||||||
// task will display the line 2 from beginning and suspend
|
// task will display the line 2 from beginning and suspend
|
||||||
displayer.state = DISPLAYER_IDLE;
|
displayer.state = DISPLAYER_IDLE;
|
||||||
|
displayer_artwork(NULL);
|
||||||
display_bus(&displayer, DISPLAY_BUS_GIVE);
|
display_bus(&displayer, DISPLAY_BUS_GIVE);
|
||||||
break;
|
break;
|
||||||
case DISPLAYER_SHUTDOWN:
|
case DISPLAYER_SHUTDOWN:
|
||||||
// let the task self-suspend (we might be doing i2c_write)
|
// let the task self-suspend (we might be doing i2c_write)
|
||||||
GDS_SetTextWidth(display, 0);
|
GDS_SetTextWidth(display, 0);
|
||||||
GDS_Clear(display, GDS_COLOR_BLACK);
|
displayer_artwork(NULL);
|
||||||
displayer.state = DISPLAYER_DOWN;
|
displayer.state = DISPLAYER_DOWN;
|
||||||
display_bus(&displayer, DISPLAY_BUS_GIVE);
|
display_bus(&displayer, DISPLAY_BUS_GIVE);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "accessors.h"
|
#include "accessors.h"
|
||||||
#include "network_services.h"
|
#include "network_services.h"
|
||||||
|
#include "tools.h"
|
||||||
#include "cspot_private.h"
|
#include "cspot_private.h"
|
||||||
#include "cspot_sink.h"
|
#include "cspot_sink.h"
|
||||||
|
|
||||||
@@ -87,6 +88,19 @@ const static actrls_t controls = {
|
|||||||
cspot_volume_down, cspot_volume_up, cspot_toggle// knob left, knob_right, knob push
|
cspot_volume_down, cspot_volume_up, cspot_toggle// knob left, knob_right, knob push
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* Download callback
|
||||||
|
*/
|
||||||
|
void got_artwork(uint8_t* data, size_t len, void *context) {
|
||||||
|
if (data) {
|
||||||
|
ESP_LOGI(TAG, "got artwork of %zu bytes", len);
|
||||||
|
displayer_artwork(data);
|
||||||
|
free(data);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "artwork error or too large %zu", len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* Command handler
|
* Command handler
|
||||||
*/
|
*/
|
||||||
@@ -106,7 +120,7 @@ static bool cmd_handler(cspot_event_t event, ...) {
|
|||||||
switch(event) {
|
switch(event) {
|
||||||
case CSPOT_SETUP:
|
case CSPOT_SETUP:
|
||||||
actrls_set(controls, false, NULL, actrls_ir_action);
|
actrls_set(controls, false, NULL, actrls_ir_action);
|
||||||
displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", false);
|
displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", true);
|
||||||
break;
|
break;
|
||||||
case CSPOT_PLAY:
|
case CSPOT_PLAY:
|
||||||
displayer_control(DISPLAYER_TIMER_RUN);
|
displayer_control(DISPLAYER_TIMER_RUN);
|
||||||
@@ -129,6 +143,11 @@ static bool cmd_handler(cspot_event_t event, ...) {
|
|||||||
uint32_t sample_rate = va_arg(args, uint32_t);
|
uint32_t sample_rate = va_arg(args, uint32_t);
|
||||||
int duration = va_arg(args, int);
|
int duration = va_arg(args, int);
|
||||||
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
|
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
|
||||||
|
char *artwork = va_arg(args, char*);
|
||||||
|
if (artwork) {
|
||||||
|
ESP_LOGI(TAG, "requesting artwork %s", artwork);
|
||||||
|
http_download(artwork, 128*1024, got_artwork, NULL);
|
||||||
|
}
|
||||||
displayer_metadata(artist, album, title);
|
displayer_metadata(artist, album, title);
|
||||||
displayer_timer(DISPLAYER_ELAPSED, loaded ? -1 : 0, duration);
|
displayer_timer(DISPLAYER_ELAPSED, loaded ? -1 : 0, duration);
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
idf_component_register( SRCS operator.cpp tools.c trace.c
|
idf_component_register( SRCS operator.cpp tools.c trace.c
|
||||||
REQUIRES _override esp_common pthread
|
REQUIRES _override esp_common pthread
|
||||||
|
PRIV_REQUIRES esp_http_client esp-tls
|
||||||
INCLUDE_DIRS .
|
INCLUDE_DIRS .
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
#include "esp_tls.h"
|
||||||
|
#include "esp_http_client.h"
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
@@ -171,3 +175,104 @@ char * strdup_psram(const char * source){
|
|||||||
}
|
}
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* URL download
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *user_context;
|
||||||
|
http_download_cb_t callback;
|
||||||
|
size_t max, bytes;
|
||||||
|
bool abort;
|
||||||
|
uint8_t *data;
|
||||||
|
char *url;
|
||||||
|
} http_context_t;
|
||||||
|
|
||||||
|
static void http_downloader(void *arg);
|
||||||
|
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
|
||||||
|
|
||||||
|
void http_download(char *url, size_t max, http_download_cb_t callback, void *context) {
|
||||||
|
http_context_t *http_context = (http_context_t*) heap_caps_calloc(sizeof(http_context_t), 1, MALLOC_CAP_SPIRAM);
|
||||||
|
|
||||||
|
http_context->callback = callback;
|
||||||
|
http_context->user_context = context;
|
||||||
|
http_context->max = max;
|
||||||
|
http_context->url = strdup(url);
|
||||||
|
|
||||||
|
xTaskCreate(http_downloader, "downloader", 4*1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void http_downloader(void *arg) {
|
||||||
|
http_context_t *http_context = (http_context_t*) arg;
|
||||||
|
esp_http_client_config_t config = {
|
||||||
|
.url = http_context->url,
|
||||||
|
.event_handler = http_event_handler,
|
||||||
|
.user_data = http_context,
|
||||||
|
//.cert_pem = howsmyssl_com_root_cert_pem_start,
|
||||||
|
//.skip_cert_common_name_check = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||||
|
esp_http_client_perform(client);
|
||||||
|
// esp_http_client_cleanup(client);
|
||||||
|
|
||||||
|
free(http_context->url);
|
||||||
|
free(http_context);
|
||||||
|
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t http_event_handler(esp_http_client_event_t *evt) {
|
||||||
|
http_context_t *http_context = (http_context_t*) evt->user_data;
|
||||||
|
|
||||||
|
if (http_context->abort) return ESP_FAIL;
|
||||||
|
|
||||||
|
switch(evt->event_id) {
|
||||||
|
case HTTP_EVENT_ERROR:
|
||||||
|
http_context->callback(NULL, 0, http_context->user_context);
|
||||||
|
http_context->abort = true;
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_HEADER:
|
||||||
|
if (!strcasecmp(evt->header_key, "Content-Length")) {
|
||||||
|
size_t len = atoi(evt->header_value);
|
||||||
|
if (!len || len > http_context->max) {
|
||||||
|
ESP_LOGI(TAG, "content-length null or too large %zu / %zu", len, http_context->max);
|
||||||
|
http_context->abort = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_DATA: {
|
||||||
|
size_t len = esp_http_client_get_content_length(evt->client);
|
||||||
|
if (!http_context->data) {
|
||||||
|
if ((http_context->data = (uint8_t*) malloc(len)) == NULL) {
|
||||||
|
http_context->abort = true;
|
||||||
|
ESP_LOGE(TAG, "gailed to allocate memory for output buffer %zu", len);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(http_context->data + http_context->bytes, evt->data, evt->data_len);
|
||||||
|
http_context->bytes += evt->data_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTTP_EVENT_ON_FINISH:
|
||||||
|
http_context->callback(http_context->data, http_context->bytes, http_context->user_context);
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_DISCONNECTED: {
|
||||||
|
int mbedtls_err = 0;
|
||||||
|
esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "HTTP download disconnect %d", err);
|
||||||
|
if (http_context->data) free(http_context->data);
|
||||||
|
http_context->callback(NULL, 0, http_context->user_context);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ char* strdup_psram(const char * source);
|
|||||||
const char* str_or_unknown(const char * str);
|
const char* str_or_unknown(const char * str);
|
||||||
const char* str_or_null(const char * str);
|
const char* str_or_null(const char * str);
|
||||||
|
|
||||||
|
typedef void (*http_download_cb_t)(uint8_t* data, size_t len, void *context);
|
||||||
|
void http_download(char *url, size_t max, http_download_cb_t callback, void *context);
|
||||||
|
|
||||||
extern const char unknown_string_placeholder[];
|
extern const char unknown_string_placeholder[];
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
Reference in New Issue
Block a user