Prechádzať zdrojové kódy

artwork for Spotify

Philippe G 3 rokov pred
rodič
commit
f09a95cc8b

+ 8 - 6
components/display/display.c

@@ -52,7 +52,8 @@ static EXT_RAM_ATTR struct {
 		bool visible;
 	} duration;
 	struct {
-		bool enable, fit;
+		bool enable, active;
+		bool fit;
 		bool updated;
 		int tick;
 		int offset;
@@ -245,7 +246,7 @@ static void displayer_task(void *args) {
 				GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
 				
 				// 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");
 					displayer_artwork((uint8_t*) default_artwork);
 				}	
@@ -265,7 +266,7 @@ static void displayer_task(void *args) {
  * 
  */
 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 y = x ? 0 : 32;
@@ -420,7 +421,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 	switch(cmd) {
 	case DISPLAYER_ACTIVATE: {	
 		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);
 		displayer.header[HEADER_SIZE] = '\0';
 		displayer.state = DISPLAYER_ACTIVE;
@@ -431,19 +432,20 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 		displayer.duration.visible = false;
 		displayer.offset = displayer.boundary = 0;
 		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);
 		break;
 	}	
 	case DISPLAYER_SUSPEND:		
 		// task will display the line 2 from beginning and suspend
 		displayer.state = DISPLAYER_IDLE;
+		displayer_artwork(NULL);
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;		
 	case DISPLAYER_SHUTDOWN:
 		// let the task self-suspend (we might be doing i2c_write)
 		GDS_SetTextWidth(display, 0);
-		GDS_Clear(display, GDS_COLOR_BLACK); 
+		displayer_artwork(NULL);
 		displayer.state = DISPLAYER_DOWN;
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;

+ 20 - 1
components/spotify/cspot_sink.c

@@ -13,6 +13,7 @@
 #include "display.h"
 #include "accessors.h"
 #include "network_services.h"
+#include "tools.h"
 #include "cspot_private.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
 };
 
+/****************************************************************************************
+ * 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
  */
@@ -106,7 +120,7 @@ static bool cmd_handler(cspot_event_t event, ...) {
 	switch(event) {
 	case CSPOT_SETUP:
 		actrls_set(controls, false, NULL, actrls_ir_action);
-		displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", false);
+		displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", true);
 		break;
 	case CSPOT_PLAY:
 		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);
 		int duration = va_arg(args, int);
 		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_timer(DISPLAYER_ELAPSED, loaded ? -1 : 0, duration);
 		loaded = false;

+ 1 - 0
components/tools/CMakeLists.txt

@@ -1,5 +1,6 @@
 idf_component_register( SRCS operator.cpp tools.c trace.c
 						REQUIRES _override esp_common pthread 
+						PRIV_REQUIRES esp_http_client esp-tls
 						INCLUDE_DIRS .
 )
 

+ 105 - 0
components/tools/tools.c

@@ -11,7 +11,11 @@
 #include <stdint.h>
 #include <string.h>
 #include <ctype.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
 #include "tools.h"
+#include "esp_tls.h"
+#include "esp_http_client.h"
 #include "esp_heap_caps.h"
 #include "esp_log.h"
 
@@ -171,3 +175,104 @@ char * strdup_psram(const char * source){
 	}
 	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;
+}
+ 

+ 3 - 0
components/tools/tools.h

@@ -53,6 +53,9 @@ char* 		strdup_psram(const char * source);
 const char* str_or_unknown(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[];
 
 #ifdef __cplusplus