浏览代码

Merge remote-tracking branch 'origin/master' into master-cmake

Sebastien 5 年之前
父节点
当前提交
70b6d54af7

+ 4 - 11
components/display/SSD1306.c

@@ -74,7 +74,7 @@ static void Update( struct GDS_Device* Device ) {
 			CurrentPage = p + 1;
 			
 			// actual write
-			Device->WriteData( Device, Private->Shadowbuffer + p*width + first, last - first + 1 );
+			Device->WriteData( Device, Private->Shadowbuffer + p*width + first, last - first + 1);
 		}
 	}	
 #else	
@@ -114,10 +114,6 @@ static bool Init( struct GDS_Device* Device ) {
 	// charge pump regulator, do direct init
 	Device->WriteCommand( Device, 0x8D );
 	Device->WriteCommand( Device, 0x14 ); 
-	
-	// set Clocks
-    Device->WriteCommand( Device, 0xD5 );
-    Device->WriteCommand( Device, ( 0x08 << 4 ) | 0x00 );
 			
 	// COM pins HW config (alternative:EN if 64, DIS if 32, remap:DIS) - some display might need something different
 	Device->WriteCommand( Device, 0xDA );
@@ -126,12 +122,6 @@ static bool Init( struct GDS_Device* Device ) {
 	// MUX Ratio
     Device->WriteCommand( Device, 0xA8 );
     Device->WriteCommand( Device, Device->Height - 1);
-	// Page & GDDRAM Start Column High/Low
-	/*
-    Device->WriteCommand( Device, 0x00 );
-	Device->WriteCommand( Device, 0x10 );
-	Device->WriteCommand( Device, 0xB0 );
-	*/
 	// Display Offset
     Device->WriteCommand( Device, 0xD3 );
     Device->WriteCommand( Device, 0 );
@@ -143,6 +133,9 @@ static bool Init( struct GDS_Device* Device ) {
 	Device->SetHFlip( Device, false );
 	// no Display Inversion
     Device->WriteCommand( Device, 0xA6 );
+	// set Clocks
+    Device->WriteCommand( Device, 0xD5 );
+    Device->WriteCommand( Device, ( 0x08 << 4 ) | 0x00 );
 	// set Adressing Mode Horizontal
 	Device->WriteCommand( Device, 0x20 );
 	Device->WriteCommand( Device, 0 );

+ 4 - 0
components/display/core/gds.c

@@ -60,6 +60,10 @@ void GDS_Clear( struct GDS_Device* Device, int Color ) {
 }
 
 void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
+	// -1 means up to width/height
+	if (x2 < 0) x2 = Device->Width - 1;
+	if (y2 < 0) y2 = Device->Height - 1;
+	
 	// driver can provide own optimized clear window
 	if (Device->ClearWindow) {
 		Device->ClearWindow( Device, x1, y1, x2, y2, Color );

+ 3 - 2
components/display/core/gds_draw.c

@@ -212,6 +212,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
 		uint8_t *optr = Device->Framebuffer;
 		int LineLen = Device->Width >> 1;
 		Height >>= 3;
+		Color &= 0x0f;
 		for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
 			uint8_t Byte = BitReverseTable256[*Data++];
 			// we need to linearize code to let compiler better optimize
@@ -223,7 +224,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
-				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; 
 			} else {
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
@@ -232,7 +233,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
-				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
 			}	
 			// end of a column, move to next one
 			if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }		

+ 3 - 1
components/display/core/gds_image.c

@@ -218,8 +218,10 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int
 		
 		// then place it
 		if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = (Device->Width + x - Context.Width) / 2;
+		else if (Fit & GDS_IMAGE_RIGHT) Context.XOfs = Device->Width - Context.Width;
 		if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = (Device->Height + y - Context.Height) / 2;
-		
+		else if (Fit & GDS_IMAGE_BOTTOM) Context.YOfs = Device->Height - Context.Height;
+
 		Context.XMin = x - Context.XOfs;
 		Context.YMin = y - Context.YOfs;
 					

+ 4 - 1
components/display/core/gds_image.h

@@ -9,8 +9,11 @@ struct GDS_Device;
 
 enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
 
-#define GDS_IMAGE_TOP		0x00
+#define GDS_IMAGE_LEFT		0x00
 #define GDS_IMAGE_CENTER_X	0x01
+#define GDS_IMAGE_RIGHT		0x04
+#define GDS_IMAGE_TOP		0x00
+#define GDS_IMAGE_BOTTOM	0x08
 #define GDS_IMAGE_CENTER_Y	0x02
 #define GDS_IMAGE_CENTER	(GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y)
 #define GDS_IMAGE_FIT		0x10

+ 1 - 1
components/display/core/gds_private.h

@@ -167,7 +167,7 @@ static inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int
 	uint8_t* FBOffset;
 
     FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
-	*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color  & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
+	*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
 }
 
 static inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {

+ 18 - 14
components/squeezelite/decode_external.c

@@ -53,7 +53,7 @@ static raop_event_t	raop_state;
 static EXT_RAM_ATTR struct {
 	bool enabled;
 	int sum, count, win, errors[SYNC_WIN_RUN];
-	u32_t len;
+	s32_t len;
 	u32_t start_time, playtime;
 } raop_sync;
 
@@ -210,20 +210,21 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 				// how many ms have we really played
 				ms = now - output.updated + ((u64_t) (output.frames_played_dmp - output.device_frames) * 1000) / RAOP_SAMPLE_RATE;
 				error = ms - (now - raop_sync.start_time);
+				
 				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, now - raop_sync.start_time, ms - (now - raop_sync.start_time));
 			} else {	
+				u32_t level = _buf_used(outputbuf);
+				
 				// in how many ms will the most recent block play 
-				ms = ((u64_t) ((_buf_used(outputbuf) - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 1000) / RAOP_SAMPLE_RATE - (now - output.updated);
-				error = (raop_sync.playtime - now) - ms;
+				ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
 				
-				// make sure impact of erroneous point is limited, but taken into account 
-				if (abs(error) > 1000) {
-					LOG_INFO("limiting erroneous sync point %d", error);
-					error = (error > 0) ? SYNC_WIN_RUN : -SYNC_WIN_RUN;
-				}
-								
-				LOG_DEBUG("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, error);
-				LOG_DEBUG("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
+				// when outputbuf is empty, it means we have a network black-out or something
+				error = level ? (raop_sync.playtime - now) - ms : 0;
+				
+				if (loglevel == lDEBUG || !level) {
+					LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
+					LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
+				}	
 			}	
 			
 			// calculate sum, error and update sliding window
@@ -232,7 +233,10 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 			error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
 			
 			// move to normal mode if possible
-			if (raop_sync.win == SYNC_WIN_START && raop_sync.count >= SYNC_WIN_START && abs(error) < 10) raop_sync.win = SYNC_WIN_RUN;
+			if (raop_sync.win == SYNC_WIN_START && raop_sync.count >= SYNC_WIN_START && abs(error) < 10) {
+				raop_sync.win = SYNC_WIN_RUN;
+				LOG_INFO("switching to slow sync mode %u", raop_sync.win);
+			}	
 
 			// wait till e have enough data or there is a strong deviation
 			if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) { 
@@ -241,11 +245,11 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 				if (error < 0) {
 					output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
 					output.state = OUTPUT_SKIP_FRAMES;					
-					LOG_INFO("skipping %u frames", output.skip_frames);
+					LOG_INFO("skipping %u frames (count:%d)", output.skip_frames, raop_sync.count);
 				} else {
 					output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000;
 					output.state = OUTPUT_PAUSE_FRAMES;
-					LOG_INFO("pausing for %u frames", output.pause_frames);
+					LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
 				}
 
 				// reset sliding window		

+ 194 - 43
components/squeezelite/display.c

@@ -26,6 +26,7 @@
 #include "gds.h"
 #include "gds_text.h"
 #include "gds_draw.h"
+#include "gds_image.h"
 
 #pragma pack(push, 1)
 
@@ -59,6 +60,14 @@ struct grfg_packet {
 	u16_t  width;		// # of pixels of scrollable
 };
 
+struct grfa_packet {
+	char  opcode[4];
+	u32_t length;
+	u16_t x;
+	u16_t y;	
+	u32_t offset;
+};
+
 struct visu_packet {
 	char  opcode[4];
 	u8_t which;
@@ -77,6 +86,29 @@ struct visu_packet {
 			u32_t bars;
 			u32_t spectrum_scale;
 		};	
+		struct {
+			u32_t mono;
+			u32_t bandwidth;
+			u32_t preemph;	
+			struct {
+				u32_t pos;
+				u32_t width;
+				u32_t orient;
+				u32_t bar_width;
+				u32_t bar_space;
+				u32_t clipping;
+				u32_t bar_intens;
+				u32_t bar_cap_intens;
+			} channels[2];
+		};
+		struct {
+			u32_t mono;
+			u32_t style;
+			struct {
+				u32_t pos;
+				u32_t width;
+			} channels[2];
+		} classical_vu;	
 	};	
 };
 
@@ -130,7 +162,15 @@ static struct scroller_s {
 	u32_t width;
 } scroller;
 
+static struct {
+	u8_t *data;
+	u32_t size;
+	u16_t x, y;
+	bool enable;
+} artwork;
+
 #define MAX_BARS	32
+#define VISU_ESP32	0x10
 static EXT_RAM_ATTR struct {
 	int bar_gap, bar_width, bar_border;
 	struct {
@@ -142,6 +182,11 @@ static EXT_RAM_ATTR struct {
 	enum { VISU_BLANK, VISU_VUMETER, VISU_SPECTRUM, VISU_WAVEFORM } mode;
 	int speed, wake;	
 	float fft[FFT_LEN*2], samples[FFT_LEN*2], hanning[FFT_LEN];
+	struct {
+		u8_t *frame;
+		int width;
+		bool active;
+	} back;		
 } visu;
 
 #define ANIM_NONE		  0x00
@@ -174,6 +219,7 @@ static void grfe_handler( u8_t *data, int len);
 static void grfb_handler(u8_t *data, int len);
 static void grfs_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
+static void grfa_handler(u8_t *data, int len);
 static void visu_handler(u8_t *data, int len);
 
 static void displayer_task(void* arg);
@@ -205,6 +251,33 @@ static void displayer_task(void* arg);
 			ANIM_SCREEN_2     0x08 # For scrollonce only, screen 2 was scrolling
 */
 
+/* classical visu not our specific version)
+ Parameters for the spectrum analyzer:
+   0 - Channels: stereo == 0, mono == 1
+   1 - Bandwidth: 0..22050Hz == 0, 0..11025Hz == 1
+   2 - Preemphasis in dB per KHz
+ Left channel parameters:
+   3 - Position in pixels
+   4 - Width in pixels
+   5 - orientation: left to right == 0, right to left == 1
+   6 - Bar width in pixels
+   7 - Bar spacing in pixels
+   8 - Clipping: show all subbands == 0, clip higher subbands == 1
+   9 - Bar intensity (greyscale): 1-3
+   10 - Bar cap intensity (greyscale): 1-3
+ Right channel parameters (not required for mono):
+   11-18 - same as left channel parameters
+
+ Parameters for the vumeter:
+   0 - Channels: stereo == 0, mono == 1
+   1 - Style: digital == 0, analog == 1
+ Left channel parameters:
+   2 - Position in pixels
+   3 - Width in pixels
+ Right channel parameters (not required for mono):
+   4-5 - same as left channel parameters
+*/
+
 /****************************************************************************************
  * 
  */
@@ -226,6 +299,7 @@ bool sb_display_init(void) {
 	// create visu configuration
 	visu.bar_gap = 1;
 	visu.speed = 100;
+	visu.back.frame = calloc(1, (displayer.width * displayer.height) / 8);
 	dsps_fft2r_init_fc32(visu.fft, FFT_LEN);
 	dsps_wind_hann_f32(visu.hanning, FFT_LEN);
 		
@@ -300,7 +374,7 @@ static void send_server(void) {
 		pkt_header.length = htonl(sizeof(pkt_header) - 8);
 		pkt_header.mode = ANIC_resp;
 
-		send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
+		send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
 		
 		ANIC_resp = ANIM_NONE;
 	}	
@@ -308,18 +382,22 @@ static void send_server(void) {
 	if (SETD_width) {
 		struct SETD_header pkt_header;
 		
-		LOG_INFO("sending width %u", SETD_width);	
-		
 		memset(&pkt_header, 0, sizeof(pkt_header));
 		memcpy(&pkt_header.opcode, "SETD", 4);
 
 		pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
-		pkt_header.length = htonl(sizeof(pkt_header) +  2 - 8);
+		pkt_header.length = htonl(sizeof(pkt_header) +  4 - 8);
+		
+		u16_t height = GDS_GetHeight(display);
+		LOG_INFO("sending dimension %ux%u", SETD_width, height);	
 
 		SETD_width = htons(SETD_width);
-		send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
-		send_packet((uint8_t*) &SETD_width, 2);
-
+		height = htons(height);
+		
+		send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
+		send_packet((uint8_t *) &SETD_width, 2);
+		send_packet((uint8_t *) &height, 2);
+		
 		SETD_width = 0;
 	}	
 	
@@ -360,6 +438,8 @@ static bool handler(u8_t *data, int len){
 		grfs_handler(data, len);		
 	} else if (!strncmp((char*) data, "grfg", 4)) {
 		grfg_handler(data, len);
+	} else if (!strncmp((char*) data, "grfa", 4)) {
+		grfa_handler(data, len);		
 	} else if (!strncmp((char*) data, "visu", 4)) {
 		visu_handler(data, len);
 	} else {
@@ -495,7 +575,7 @@ static void grfe_handler( u8_t *data, int len) {
 	scroller.active = false;
 	
 	// we are not in control or we are displaying visu on a small screen, do not do screen update
-	if (visu.mode && !visu.col && visu.row < SB_HEIGHT) {
+	if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < SB_HEIGHT) {
 		xSemaphoreGive(displayer.mutex);
 		return;
 	}	
@@ -509,6 +589,16 @@ static void grfe_handler( u8_t *data, int len) {
 	
 		// draw new frame, it might be less than full screen (small visu)
 		int width = ((len - sizeof(struct grfe_packet)) * 8) / displayer.height;
+		
+		// when doing screensaver, that frame becomes a visu background
+		if (!(visu.mode & VISU_ESP32)) {
+			visu.back.width = width;
+			memset(visu.back.frame, 0, (displayer.width * displayer.height) / 8);
+			memcpy(visu.back.frame, data + sizeof(struct grfe_packet), (width * displayer.height) / 8);
+			// this is a bit tricky but basically that checks if frame if full of 0
+			visu.back.active = *visu.back.frame || memcmp(visu.back.frame, visu.back.frame + 1, width - 1);
+		}
+		
 		GDS_DrawBitmapCBR(display, data + sizeof(struct grfe_packet), width, displayer.height, GDS_COLOR_WHITE);
 		GDS_Update(display);
 	}	
@@ -548,7 +638,7 @@ static void grfs_handler(u8_t *data, int len) {
 	int size = len - sizeof(struct grfs_packet);
 	int offset = htons(pkt->offset);
 	
-	LOG_DEBUG("gfrs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", 
+	LOG_DEBUG("grfs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", 
 				(int) pkt->screen,
 				(int) pkt->direction,	// 1=left, 2=right
 				htonl(pkt->pause),		// in ms	
@@ -573,9 +663,6 @@ static void grfs_handler(u8_t *data, int len) {
 		scroller.first = true;
 		scroller.overflow = false;
 		
-		// background excludes space taken by visu (if any)
-		scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0);
-		
 		// set scroller steps & beginning
 		if (pkt->direction == 1) {
 			scroller.scrolled = 0;
@@ -612,6 +699,7 @@ static void grfg_handler(u8_t *data, int len) {
 	
 	// size of scrollable area (less than background)
 	scroller.width = htons(pkt->width);
+	scroller.back.width = ((len - sizeof(struct grfg_packet)) * 8) / displayer.height;
 	memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
 		
 	// update display asynchronously (frames are organized by columns)
@@ -636,15 +724,63 @@ static void grfg_handler(u8_t *data, int len) {
 	vTaskResume(displayer.task);
 }
 
+
+/****************************************************************************************
+ * Artwork
+ */
+static void grfa_handler(u8_t *data, int len) {
+	struct grfa_packet *pkt = (struct grfa_packet*) data;
+	int size = len - sizeof(struct grfa_packet);
+	int offset = htonl(pkt->offset);
+	int length = htonl(pkt->length);
+	
+	artwork.enable = (length != 0);
+	
+	// clean up if we are disabling previously enabled artwork
+	if (!artwork.enable) {
+		if (artwork.size) GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
+		return;
+	}	
+	
+	// new grfa artwork, allocate memory
+	if (!offset) {	
+		// same trick to clean current/previous window
+		if (artwork.size) {
+			GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
+			artwork.size = 0;
+		}
+		
+		// now use new parameters
+		artwork.x = htons(pkt->x);
+		artwork.y = htons(pkt->y);
+		if (artwork.data) free(artwork.data);
+		artwork.data = malloc(length);
+	}	
+	
+	// copy artwork data
+	memcpy(artwork.data + offset, data + sizeof(struct grfa_packet), size);
+	artwork.size += size;
+	if (artwork.size == length) {
+		GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
+		GDS_DrawJPEG(display, artwork.data, artwork.x, artwork.y, artwork.y < SB_HEIGHT ? (GDS_IMAGE_RIGHT | GDS_IMAGE_TOP) : GDS_IMAGE_CENTER);
+		free(artwork.data);
+		artwork.data = NULL;
+	} 
+	
+	LOG_INFO("gfra l:%u x:%hu, y:%hu, o:%u s:%u", length, artwork.x, artwork.y, offset, size);
+}
+
 /****************************************************************************************
  * Update visualization bars
  */
 static void visu_update(void) {
 	// no need to protect against no woning the display as we are playing	
 	if (pthread_mutex_trylock(&visu_export.mutex)) return;
+	
+	int mode = visu.mode & ~VISU_ESP32;
 				
 	// not enough samples
-	if (visu_export.level < (visu.mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) * 2 && visu_export.running) {
+	if (visu_export.level < (mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) * 2 && visu_export.running) {
 		pthread_mutex_unlock(&visu_export.mutex);
 		return;
 	}
@@ -654,7 +790,7 @@ static void visu_update(void) {
 	
 	if (visu_export.running) {
 					
-		if (visu.mode == VISU_VUMETER) {
+		if (mode == VISU_VUMETER) {
 			s16_t *iptr = visu_export.buffer;
 			
 			// calculate sum(L²+R²), try to not overflow at the expense of some precision
@@ -667,7 +803,7 @@ static void visu_update(void) {
 		
 			// convert to dB (1 bit remaining for getting X²/N, 60dB dynamic starting from 0dBFS = 3 bits back-off)
 			for (int i = visu.n; --i >= 0;) {	 
-				visu.bars[i].current = 32 * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> 1)) - 0.2543f);
+				visu.bars[i].current = SB_HEIGHT * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> 1)) - 0.2543f);
 				if (visu.bars[i].current > 31) visu.bars[i].current = 31;
 				else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
 			}
@@ -709,7 +845,7 @@ static void visu_update(void) {
 				}	
 			
 				// convert to dB and bars, same back-off
-				if (power) visu.bars[i].current = 32 * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f);
+				if (power) visu.bars[i].current = SB_HEIGHT * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f);
 				if (visu.bars[i].current > 31) visu.bars[i].current = 31;
 				else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
 			}	
@@ -724,6 +860,11 @@ static void visu_update(void) {
 	int clear = 0;
 	for (int i = visu.n; --i >= 0;) clear = max(clear, visu.bars[i].max);
 	if (clear) GDS_ClearExt(display, false, false, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
+	
+	// draw background if we are in screensaver mode
+	if (!(visu.mode & VISU_ESP32) && visu.back.active) {
+		GDS_DrawBitmapCBR(display, visu.back.frame, visu.back.width, displayer.height, GDS_COLOR_WHITE);
+	}	
 
 	// there is much more optimization to be done here, like not redrawing bars unless needed
 	for (int i = visu.n; --i >= 0;) {
@@ -778,37 +919,47 @@ static void visu_handler( u8_t *data, int len) {
 	visu.mode = pkt->which;
 	
 	// little trick to clean the taller screens when switching visu 
-	if (visu.row >= SB_HEIGHT) GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1);
+	if (visu.row >= SB_HEIGHT) GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
 	
 	if (visu.mode) {
-		if (pkt->count >= 4) {
-			// small visu, then go were we are told to
-			pkt->height = htonl(pkt->height);
-			pkt->row = htonl(pkt->row);
-			pkt->col = htonl(pkt->col);
-
-			visu.width = htonl(pkt->width);
-			visu.height = pkt->height ? pkt->height : SB_HEIGHT;
-			visu.col = pkt->col < 0 ? displayer.width + pkt->col : pkt->col;
-			visu.row = pkt->row < 0 ? GDS_GetHeight(display) + pkt->row : pkt->row;
-			visu.border =  htonl(pkt->border);
-			bars = htonl(pkt->bars);
-			visu.spectrum_scale = htonl(pkt->spectrum_scale) / 100.;
-			
-			// might have a race condition with scroller message, so update width in case
-			if (scroller.active) scroller.back.width = displayer.width - visu.width;
+		// these will be overidden if necessary
+		visu.col = visu.border = 0;
+		visu.width = displayer.width;				
+		
+		// what type of visu
+		if (visu.mode & VISU_ESP32) {
+			if (pkt->count >= 4) {
+				// more than 4 parameters, this is small visu, then go were we are told to
+				pkt->height = htonl(pkt->height);
+				pkt->row = htonl(pkt->row);
+				pkt->col = htonl(pkt->col);
+
+				visu.width = htonl(pkt->width);
+				visu.height = pkt->height ? pkt->height : SB_HEIGHT;
+				visu.col = pkt->col < 0 ? displayer.width + pkt->col : pkt->col;
+				visu.row = pkt->row < 0 ? GDS_GetHeight(display) + pkt->row : pkt->row;
+				visu.border =  htonl(pkt->border);
+				bars = htonl(pkt->bars);
+				visu.spectrum_scale = htonl(pkt->spectrum_scale) / 100.;
+			} else {
+				// full screen visu, try to use bottom screen if available
+				visu.height = GDS_GetHeight(display) > SB_HEIGHT ? GDS_GetHeight(display) - SB_HEIGHT : GDS_GetHeight(display);
+				bars = htonl(pkt->full.bars);
+				visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.;
+				visu.row = GDS_GetHeight(display) - visu.height;			
+			}	
 		} else {
-			// full screen visu, try to use bottom screen if available
-			visu.width = displayer.width;
-			visu.height = GDS_GetHeight(display) > SB_HEIGHT ? GDS_GetHeight(display) - SB_HEIGHT : GDS_GetHeight(display);
-			visu.col = visu.border = 0;
-			visu.row = GDS_GetHeight(display) - visu.height;			
-			bars = htonl(pkt->full.bars);
-			visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.;
-		}
+			// classical (screensaver) mode, don't try to optimize screen usage & force some params
+			visu.row = 0;
+			visu.height = SB_HEIGHT;
+			visu.spectrum_scale = 0.25;				
+			if (artwork.enable && artwork.y < SB_HEIGHT) visu.width = artwork.x - 1;
+			if (visu.mode == VISU_SPECTRUM) bars = visu.width / (htonl(pkt->channels[0].bar_width) + htonl(pkt->channels[0].bar_space));
+			if (bars > MAX_BARS) bars = MAX_BARS;
+		}	
 		
 		// try to adapt to what we have
-		if (visu.mode == VISU_SPECTRUM) {
+		if ((visu.mode & ~VISU_ESP32) == VISU_SPECTRUM) {
 			visu.n = bars ? bars : MAX_BARS;
 			if (visu.spectrum_scale <= 0 || visu.spectrum_scale > 0.5) visu.spectrum_scale = 0.5;
 			spectrum_limits(0, visu.n, 0);
@@ -836,7 +987,7 @@ static void visu_handler( u8_t *data, int len) {
 		// reset bars maximum
 		for (int i = visu.n; --i >= 0;) visu.bars[i].max = 0;
 				
-		GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1);
+		GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
 		
 		LOG_INFO("Visualizer with %u bars of width %d:%d:%d:%d (%w:%u,h:%u,c:%u,r:%u,s:%.02f)", visu.n, visu.bar_border, visu.bar_width, visu.bar_gap, visu.border, visu.width, visu.height, visu.col, visu.row, visu.spectrum_scale);
 	} else {

二进制
plugin/SqueezeESP32.zip


+ 32 - 13
plugin/SqueezeESP32/Graphics.pm

@@ -14,6 +14,8 @@ my $VISUALIZER_NONE = 0;
 my $VISUALIZER_VUMETER = 1;
 my $VISUALIZER_SPECTRUM_ANALYZER = 2;
 my $VISUALIZER_WAVEFORM = 3;
+my $VISUALIZER_VUMETER_ESP32 = 0x11;
+my $VISUALIZER_SPECTRUM_ANALYZER_ESP32 = 0x12;
 
 {
 	#__PACKAGE__->mk_accessor('array', 'modes');
@@ -71,7 +73,12 @@ sub displayWidth {
 	}
 	
 	if ($display->widthOverride) {
-		return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
+		my $artwork = $prefs->client($client)->get('artwork');
+		if ($artwork->{'enable'} && $artwork->{'y'} < 32 && $client->isPlaying) {
+			return $artwork->{x} + ($display->modes->[$mode || 0]{_width} || 0);
+		} else {
+			return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
+		}	
 	} else {
 		return $display->modes->[$mode || 0]{width};
 	}	
@@ -100,10 +107,22 @@ sub build_modes {
 	my $client = shift->client;
 	my $cprefs = $prefs->client($client);
 	
-	my $width = shift || $cprefs->get('width') || 128;
+	my $width = $cprefs->get('width') || 128;
+	my $artwork = $cprefs->get('artwork');
+	
+	# if artwork is in main display, reduce width
+	$width = $artwork->{'x'} if $artwork->{'enable'} && $artwork->{y} < 32;
+	
 	my $small_VU = $cprefs->get('small_VU');
 	my $spectrum = $cprefs->get('spectrum');
 	
+	my $small_spectrum_pos = { x => $width - int ($spectrum->{small}->{size} * $width / 100), 
+						 width => int ($spectrum->{small}->{size} * $width / 100),
+			};
+	my $small_VU_pos = { x => $width - int ($small_VU * $width / 100), 
+						 width => int ($small_VU * $width / 100),
+			};		
+	
 	my @modes = (
 		# mode 0
 		{ desc => ['BLANK'],
@@ -135,41 +154,41 @@ sub build_modes {
 		params => [$VISUALIZER_NONE] },
 		# mode 7
 		{ desc => ['VISUALIZER_VUMETER_SMALL'],
-		bar => 0, secs => 0,  width => $width, _width => int -($small_VU*$width/100),
+		bar => 0, secs => 0,  width => $width, _width => -$small_VU_pos->{'width'},
 		# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space)
-		params => [$VISUALIZER_VUMETER, int ($small_VU*$width/100), 32, int -($small_VU*$width/100), 0, 2] },
+		params => [$VISUALIZER_VUMETER_ESP32, $small_VU_pos->{'width'}, 32, $small_VU_pos->{'x'}, 0, 2] },
 		# mode 8
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'],
-		bar => 0, secs => 0,  width => $width, _width => int -($spectrum->{small}->{size}*$width/100),
-		# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space, bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($spectrum->{small}->{size}*$width/100), 32, int -($spectrum->{small}->{size}*$width/100), 0, 2, int ($spectrum->{small}->{size}/100*$width/$spectrum->{small}->{band}), $spectrum->{scale}] },  
+		bar => 0, secs => 0,  width => $width, _width => -$small_spectrum_pos->{'width'},
+		# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space, #bars, scale)
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $small_spectrum_pos->{width}, 32, $small_spectrum_pos->{'x'}, 0, 2, $small_spectrum_pos->{'width'} / $spectrum->{small}->{band}, $spectrum->{scale}] },  
 		# mode 9	 
 		{ desc => ['VISUALIZER_VUMETER'],
 		bar => 0, secs => 0,  width => $width,
-		params => [$VISUALIZER_VUMETER] },
+		params => [$VISUALIZER_VUMETER_ESP32] },
 		# mode 10
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER'],
 		bar => 0, secs => 0,  width => $width,
 		# extra parameters (bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
 		# mode 11	 
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'],
 		bar => 0, secs => 1,  width => $width,
-		params => [$VISUALIZER_VUMETER] },
+		params => [$VISUALIZER_VUMETER_ESP32] },
 		# mode 12
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'],
 		bar => 0, secs => 1,  width => $width,
 		# extra parameters (bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
 		# mode 13	 
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'],
 		bar => 0, secs => -1,  width => $width,
-		params => [$VISUALIZER_VUMETER] },
+		params => [$VISUALIZER_VUMETER_ESP32] },
 		# mode 14
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'],
 		bar => 0, secs => -1,  width => $width,
 		# extra parameters (bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	
 	);
 	
 	return \@modes;

+ 0 - 20
plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html

@@ -1,20 +0,0 @@
-[% PROCESS settings/header.html %]
-
-	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_BANNER" %]
-		<div>[% "PLUGIN_SQUEEZEESP32_BANNER_TEXT" | string %]</div>
-	[% END %]
-
-	<div class="prefDesc">
-	
-	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %]
-		<input type="text" class="stdedit" name="pref_width" id="pref_width" value="[% prefs.pref_width %]" size="3">
-	[% END %]
-	
-	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %]
-		<input type="number" min="10" max= "50" step="5" class="stdedit" name="pref_spectrum_scale" id="pref_spectrum_scale" value="[% prefs.pref_spectrum_scale %]" size="3">
-	[% END %]
-	
-	
-	</div>
-
-[% PROCESS settings/footer.html %]

+ 9 - 0
plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html

@@ -24,5 +24,14 @@
 		<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% prefs.pref_spectrum.full.band %]" size="3">
 	[% END %]
 	
+	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_ARTWORK" desc="PLUGIN_SQUEEZEESP32_ARTWORK_DESC" %]
+		[% "PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE" | string %]&nbsp
+		<input type="checkbox" name="pref_artwork_enable" [% IF prefs.pref_artwork.enable %] checked [% END %]>
+		[% "PLUGIN_SQUEEZEESP32_ARTWORK_X" | string %]&nbsp
+		<input type="text" class="stdedit" name="pref_artwork_x" id="artwork_x" value="[% prefs.pref_artwork.x %]" size="2">
+		[% "PLUGIN_SQUEEZEESP32_ARTWORK_Y" | string %]&nbsp
+		<input type="text" class="stdedit" name="pref_artwork_y" id="artwork_y" value="[% prefs.pref_artwork.y %]" size="2">
+	[% END %]
+	
 
 [% PROCESS settings/footer.html %]

+ 13 - 5
plugin/SqueezeESP32/Player.pm

@@ -20,24 +20,32 @@ sub playerSettingsFrame {
 	
 	my $value;
 	my $id = unpack('C', $$data_ref);
-		        
-	# New SETD command 0xfe for display width
+	
+	# New SETD command 0xfe for display width & height
 	if ($id == 0xfe) { 
 		$value = (unpack('Cn', $$data_ref))[1];
 		if ($value > 100 && $value < 400) {
 			$prefs->client($client)->set('width', $value);
-			$client->display->modes($client->display->build_modes($value));
+			$client->display->modes($client->display->build_modes);
 			$client->display->widthOverride(1, $value);
 			$client->update;
 		} 
-		$log->info("Setting player width $value for ", $client->name);
+		my $height = (unpack('Cnn', $$data_ref))[2];
+		$prefs->client($client)->set('height', $height || 0);
+		$log->info("Setting player $value" . "x" . "$height for ", $client->name);
 	}
 	
 	$client->SUPER::playerSettingsFrame($data_ref);
 }
 
-sub hasScrolling  {
+sub hasScrolling {
 	return 1;
 }
 
+sub reconnect {
+	my $client = shift;
+	$client->pluginData('artwork_md5', '');
+	$client->SUPER::reconnect(@_);
+}	
+
 1;

+ 15 - 2
plugin/SqueezeESP32/PlayerSettings.pm

@@ -30,7 +30,7 @@ sub page {
 
 sub prefs {
 	my ($class, $client) = @_;
-	my @prefs = qw(width small_VU spectrum);
+	my @prefs = qw(width small_VU spectrum artwork);
 	return ($prefs->client($client), @prefs);
 }
 
@@ -47,8 +47,20 @@ sub handler {
 							full  => { 	band => $paramRef->{'pref_spectrum_full_band'} },
 				};
 		$cprefs->set('spectrum', $spectrum);
+		my $artwork =	{	enable => $paramRef->{'pref_artwork_enable'},
+							x => $paramRef->{'pref_artwork_x'}, 
+							y => $paramRef->{'pref_artwork_y'},
+				};
+		$cprefs->set('artwork', $artwork);				
 		$client->display->modes($client->display->build_modes);
 		$client->display->update;
+		
+		# force update or disable artwork
+		if ($artwork->{'enable'}) {
+			Plugins::SqueezeESP32::Plugin::update_artwork($client, 1);
+		} else {
+			Plugins::SqueezeESP32::Plugin::disable_artwork($client);
+		}	
 	}
 	
 	# as there is nothing captured, we need to re-set these variables
@@ -56,9 +68,10 @@ sub handler {
 	
 	# here I don't know why you need to set again spectrum which is a reference
 	# to a hash. Using $paramRef->{prefs} does not work either. It seems that 
-	# soem are copies of value, some are references, can't figure out.This whole
+	# some are copies of value, some are references, can't figure out. This whole
 	# logic of "Settings" is beyond me and I really hate it
 	$paramRef->{'pref_spectrum'} = $cprefs->get('spectrum');
+	$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
 	
 	return $class->SUPER::handler($client, $paramRef);
 }

+ 71 - 0
plugin/SqueezeESP32/Plugin.pm

@@ -3,8 +3,12 @@ package Plugins::SqueezeESP32::Plugin;
 use strict;
 
 use base qw(Slim::Plugin::Base);
+
+use Digest::MD5 qw(md5);
+use List::Util qw(min);
 use Slim::Utils::Prefs;
 use Slim::Utils::Log;
+use Slim::Web::ImageProxy;
 
 my $prefs = preferences('plugin.squeezeesp32');
 
@@ -28,6 +32,73 @@ sub initPlugin {
 	$class->SUPER::initPlugin(@_);
 	Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' });
 	$log->info("Added class 100 for SqueezeESP32");
+	
+	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
+	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
+}
+
+sub onNotification {
+    my $request = shift;
+    my $client  = $request->client;
+	
+	my $reqstr     = $request->getRequestString();
+	$log->info("artwork update notification $reqstr");
+	#my $path = $request->getParam('_path');
+
+	update_artwork($client);
+}
+
+sub update_artwork {
+    my $client  = shift;
+	my $force = shift || 0;
+	my $cprefs = $prefs->client($client);
+	my $artwork = $cprefs->get('artwork');
+		
+	return unless $client->model eq 'squeezeesp32' && $artwork->{'enable'};
+
+	my $s = $artwork->{'y'} >= 32 ? $cprefs->get('height') - $artwork->{'y'} : 32;
+	$s = min($s, $cprefs->get('width') - $artwork->{'x'});
+	
+	my $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg';
+	my $body = Slim::Web::Graphics::artworkRequest($client, $path, $force, \&send_artwork, undef, HTTP::Response->new);
+	
+	send_artwork($client, undef, \$body) if $body;
+}
+
+sub send_artwork {
+	my ($client, $force, $dataref) = @_;
+	
+	# I'm not sure why we are called so often, so only send when needed
+	my $md5 = md5($$dataref);
+	return if $client->pluginData('artwork_md5') eq $md5 && !$force;
+	
+	$client->pluginData('artwork', $dataref);
+	$client->pluginData('artwork_md5', $md5);
+	
+	my $artwork = $prefs->client($client)->get('artwork');
+	my $length = length $$dataref;
+	my $offset = 0;
+	
+	$log->info("got resized artwork (length: ", length $$dataref, ")");
+	
+	my $header = pack('Nnn', $length, $artwork->{'x'}, $artwork->{'y'});
+	
+	while ($length > 0) {
+		$length = 1280 if $length > 1280;
+		$log->info("sending grfa $length");
+			
+		my $data = $header . pack('N', $offset) . substr( $$dataref, 0, $length, '' );
+			
+		$client->sendFrame( grfa => \$data );
+		$offset += $length;			
+		$length = length $$dataref;
+	}
+}	
+
+sub disable_artwork {
+	my ($client) = @_;
+	my $header = pack('N', 0);
+	$client->sendFrame( grfa => \$header );
 }
 
 1;

+ 0 - 30
plugin/SqueezeESP32/Settings.pm

@@ -1,30 +0,0 @@
-package Plugins::SqueezeESP32::Settings;
-use base qw(Slim::Web::Settings);
-
-use strict;
-
-use Slim::Utils::Prefs;
-use Slim::Utils::Log;
-
-my $log = logger('plugin.squeezeesp32');
-
-sub name {
-	return 'PLUGIN_SQUEEZEESP32';
-}
-
-sub page {
-	return 'plugins/SqueezeESP32/settings/basic.html';
-}
-
-sub prefs {
-	return (preferences('plugin.squeezeesp32'), qw(width spectrum_scale));
-}
-
-sub handler {
-	my ($class, $client, $params, $callback, @args) = @_;
-	
-	$callback->($client, $params, $class->SUPER::handler($client, $params), @args);
-}
-
-	
-1;

+ 1 - 1
plugin/SqueezeESP32/install.xml

@@ -10,6 +10,6 @@
   <name>PLUGIN_SQUEEZEESP32</name>
   <description>PLUGIN_SQUEEZEESP32_DESC</description>
   <module>Plugins::SqueezeESP32::Plugin</module>
-    <version>0.31</version>
+    <version>0.50</version>
   <creator>Philippe</creator>
 </extensions>

+ 16 - 0
plugin/SqueezeESP32/strings.txt

@@ -53,3 +53,19 @@ PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND
 		
 PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC
 	EN	The number of bands is the width of the screen divided by this factor
+	
+PLUGIN_SQUEEZEESP32_ARTWORK
+	EN	Artwork
+	
+PLUGIN_SQUEEZEESP32_ARTWORK_DESC
+	EN	When Y position is less than 32, then artwork is display at the right of the main screen and x defines the starting position
+	EN	Using artwork on less than 16-levels grayscale display if really poor quality
+
+PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE
+	EN	Enable
+	
+PLUGIN_SQUEEZEESP32_ARTWORK_X
+	EN	X
+
+PLUGIN_SQUEEZEESP32_ARTWORK_Y
+	EN	Y

+ 2 - 2
plugin/repo.xml

@@ -1,10 +1,10 @@
 <?xml version='1.0' standalone='yes'?>
 <extensions>
   <plugins>
-    <plugin version="0.31" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
+    <plugin version="0.50" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <creator>Philippe</creator>
-      <sha>6dc35a0f9f9b287d205f7532cbb642b08407a284</sha>
+      <sha>47feaf69a40ad4f87c58b34212d71e60dca99d3e</sha>
       <email>philippe_44@outlook.com</email>
       <desc lang="EN">SqueezeESP32 additional player id (100)</desc>
       <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>