shithub: riscv

Download patch

ref: 3668c38e40b94bfa3fa634ab15f3b3b4746aa224
parent: 5211d29ed470d2bfda78b03970be6dc6a6614447
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Mon Oct 17 14:45:38 EDT 2022

zuke: update ICY title from the stream metadata while playing

--- a/sys/src/cmd/audio/zuke/icy.c
+++ b/sys/src/cmd/audio/zuke/icy.c
@@ -1,14 +1,95 @@
 #include <u.h>
 #include <libc.h>
 #include <bio.h>
+#include <thread.h>
 #include "plist.h"
 #include "icy.h"
 
+typedef struct Icyaux Icyaux;
+
+struct Icyaux
+{
+	Channel *newtitle;
+	int outfd;
+	int metaint;
+};
+
+static char *
+truncate(char *s){
+	Rune … = L'…';
+	int n;
+
+	/* titles can be obnoxiously long */
+	if(strlen(s) > (n = 48-UTFmax))
+		s[n + runetochar(s+n, &…)] = 0;
+
+	return s;
+}
+
+static long
+Breadn(Biobufhdr *bp, void *addr, long nbytes)
+{
+	long n, r;
+	u8int *p;
+
+	for(n = 0, p = addr; n < nbytes; n += r, p += r){
+		if((r = Bread(bp, p, nbytes-n)) < 1)
+			break;
+	}
+
+	return n;
+}
+
+static void
+icyproc(void *b_)
+{
+	char *p, *s, *e;
+	Biobuf *b, out;
+	int n, r, sz;
+	Icyaux *aux;
+
+	threadsetname("icy/pull");
+	b = b_;
+	aux = b->aux;
+	Binit(&out, aux->outfd, OWRITE);
+	sz = aux->metaint > 4096 ? aux->metaint : 4096;
+	p = malloc(sz);
+	for(;;){
+		r = Breadn(b, p, aux->metaint > 0 ? aux->metaint : sz);
+		if(r < 1 || Bwrite(&out, p, r) != r)
+			break;
+		if(aux->metaint > 0){
+			if((n = 16*Bgetc(b)) < 0)
+				break;
+			if(Breadn(b, p, n) != n)
+				break;
+			p[n] = 0;
+			if((s = strstr(p, "StreamTitle='")) != nil && (e = strstr(s+13, "';")) != nil && e != s+13){
+				s += 13;
+				*e = 0;
+				s = strdup(truncate(s));
+				if(sendp(aux->newtitle, s) != 1){
+					free(s);
+					break;
+				}
+			}
+		}
+	}
+	free(p);
+	Bterm(b);
+	Bterm(&out);
+	chanclose(aux->newtitle);
+
+	threadexits(nil);
+}
+
 int
-icyfill(Meta *m)
+icyget(Meta *m, int outfd, Channel **newtitle)
 {
-	char *s, *s0, *e, *p, *path, *d;
-	int f, n;
+	char *s, *e, *p, *path, *d;
+	int f, r, n;
+	Icyaux *aux;
+	Biobuf *b;
 
 	path = strdup(m->path);
 	s = strchr(path, ':')+3;
@@ -25,25 +106,45 @@
 	if(f < 0)
 		return -1;
 	fprint(f, "GET /%s HTTP/0.9\r\nIcy-MetaData: 1\r\n\r\n", e ? e : "");
-	s0 = malloc(4096);
-	if((n = readn(f, s0, 4095)) > 0){
-		s0[n] = 0;
-		for(s = s0; s = strchr(s, '\n');){
-			s++;
-			if(strncmp(s, "icy-name:", 9) == 0 && (e = strchr(s, '\r')) != nil){
-				*e = 0;
-				m->artist[0] = strdup(s+9);
-				m->numartist = 1;
-				s = e+1;
-			}else if(strncmp(s, "icy-url:", 8) == 0 && (e = strchr(s, '\r')) != nil){
-				*e = 0;
-				m->title = strdup(s+8);
-				s = e+1;
-			}
+	b = Bfdopen(f, OREAD);
+	aux = mallocz(sizeof(*aux), 1);
+	aux->outfd = outfd;
+	aux->newtitle = chancreate(sizeof(char*), 2);
+	for(r = -1;;){
+		if((s = Brdline(b, '\n')) == nil)
+			break;
+		if((n = Blinelen(b)) < 2)
+			break;
+		if(n == 2 && *s == '\r'){ /* eof */
+			r = 0;
+			break;
 		}
+		s[n-2] = 0;
+		if(strncmp(s, "icy-name:", 9) == 0){
+			s = truncate(s+9);
+			if(newtitle != nil)
+				sendp(aux->newtitle, strdup(s));
+			else if(m->title == nil)
+				m->title = strdup(s);
+		}else if(newtitle == nil && strncmp(s, "icy-url:", 8) == 0 && m->numartist == 0){
+			s += 8;
+			m->artist[m->numartist++] = strdup(s);
+		}else if(strncmp(s, "icy-metaint:", 12) == 0){
+			s += 12;
+			aux->metaint = atoi(s);
+		}
 	}
-	free(s0);
-	close(f);
+	if(r < 0 || outfd < 0){
+		Bterm(b);
+		b = nil;
+		free(aux);
+	}
+	if(b != nil){
+		assert(aux->newtitle != nil);
+		b->aux = aux;
+		*newtitle = aux->newtitle;
+		proccreate(icyproc, b, mainstacksize);
+	}
 
-	return n > 0 ? 0 : -1;
+	return r;
 }
--- a/sys/src/cmd/audio/zuke/icy.h
+++ b/sys/src/cmd/audio/zuke/icy.h
@@ -1,1 +1,1 @@
-int icyfill(Meta *m);
+int icyget(Meta *m, int outfd, Channel **newtitle);
--- a/sys/src/cmd/audio/zuke/mkfile
+++ b/sys/src/cmd/audio/zuke/mkfile
@@ -13,6 +13,6 @@
 
 $O.mkplist: icy.$O plist.$O mkplist.$O
 
-$O.zuke: plist.$O zuke.$O
+$O.zuke: icy.$O plist.$O zuke.$O
 
 </sys/src/cmd/mkmany
--- a/sys/src/cmd/audio/zuke/mkplist.c
+++ b/sys/src/cmd/audio/zuke/mkplist.c
@@ -397,14 +397,16 @@
 	for(i = 0; i < argc; i++){
 		if(strncmp(argv[i], "http://", 7) == 0 || strncmp(argv[i], "https://", 8) == 0){
 			m = mallocz(sizeof(*m), 1);
-			m->title = argv[i];
 			m->path = argv[i];
 			m->filefmt = "";
-			if(icyfill(m) != 0){
+			if(icyget(m, -1, nil) != 0){
 				fprint(2, "%s: %r\n", argv[i]);
 				free(m);
-			}else
+			}else{
+				if(m->numartist == 0)
+					m->artist[m->numartist++] = argv[i];
 				sendp(cmeta, m);
+			}
 		}else{
 			if(argv[i][0] == '/')
 				dir = strdup(argv[i]);
--- a/sys/src/cmd/audio/zuke/zuke.c
+++ b/sys/src/cmd/audio/zuke/zuke.c
@@ -8,6 +8,7 @@
 #include <plumb.h>
 #include <ctype.h>
 #include "plist.h"
+#include "icy.h"
 
 #define MAX(a,b) ((a)>=(b)?(a):(b))
 #define MIN(a,b) ((a)<=(b)?(a):(b))
@@ -61,6 +62,8 @@
 	Channel *ctl;
 	Channel *ev;
 	Channel *img;
+	Channel *icytitlec;
+	char *icytitle;
 	double seek;
 	double gain;
 	int pcur;
@@ -277,7 +280,7 @@
 	int x, i, j, scrollcenter, w;
 	uvlong dur, msec;
 	Rectangle sel, r;
-	char tmp[32];
+	char tmp[32], *s;
 	Point p, sp, p₀, p₁;
 	Image *col;
 
@@ -421,7 +424,11 @@
 			for(j = 0; cols[j] != 0; j++){
 				sel.max.x = p.x + colwidth[j];
 				replclipr(back, 0, sel);
-				string(back, p, col, sp, f, getcol(getmeta(i), cols[j]));
+				if(pcurplaying == i && playercurr->icytitle != nil && cols[j] == Ptitle)
+					s = playercurr->icytitle;
+				else
+					s = getcol(getmeta(i), cols[j]);
+				string(back, p, col, sp, f, s);
 				p.x += colwidth[j] + 8;
 			}
 			replclipr(back, 0, back->r);
@@ -456,6 +463,7 @@
 
 	threadsetname("redraw");
 	while(recv(redrawc, &full) == 1){
+Again:
 		redraw_(full);
 		another = 0;
 		full = 0;
@@ -464,7 +472,7 @@
 			another = 1;
 		}
 		if(another)
-			redraw_(nbfull);
+			goto Again;
 	}
 
 	threadexits(nil);
@@ -669,12 +677,12 @@
 static void
 playerthread(void *player_)
 {
-	char *buf, cmd[64], seekpos[12], *fmt;
+	char *buf, cmd[64], seekpos[12], *fmt, *path, *icytitle;
 	Player *player;
 	Ioproc *io;
 	Image *thiscover;
 	ulong c;
-	int p[2], fd, pid, noinit, trycoverload;
+	int p[2], q[2], fd, pid, noinit, trycoverload;
 	long n, r;
 	vlong boffset, boffsetlast;
 	Meta *cur;
@@ -691,7 +699,9 @@
 restart:
 	cur = getmeta(player->pcur);
 	fmt = cur->filefmt;
+	path = cur->path;
 	fd = -1;
+	q[0] = -1;
 	if(*fmt){
 		if((fd = open(cur->path, OREAD)) < 0){
 			fprint(2, "%r\n");
@@ -702,14 +712,25 @@
 	}else{
 		sendul(player->ev, Evready);
 		chanclose(player->ev);
+		if(strncmp(cur->path, "http://", 7) == 0){ /* try icy */
+			pipe(q);
+			if(icyget(cur, q[0], &player->icytitlec) == 0){
+				fd = q[1];
+				path = nil;
+			}else{
+				close(q[0]); q[0] = -1;
+				close(q[1]);
+			}
+		}
 	}
 
 	pipe(p);
 	if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){
+		close(q[0]);
 		close(p[1]);
 		if(fd < 0)
 			fd = open("/dev/null", OREAD);
-		dup(fd, 0); close(fd);
+		dup(fd, 0); close(fd); /* fd == q[1] when it's Icy */
 		dup(p[0], 1); close(p[0]);
 		if(!debug){
 			dup(fd = open("/dev/null", OWRITE), 2);
@@ -720,7 +741,7 @@
 			snprint(seekpos, sizeof(seekpos), "%g", (double)boffset/Bps);
 			execl(cmd, cmd, boffset ? "-s" : nil, seekpos, nil);
 		}else{
-			execl("/bin/play", "play", "-o", "/fd/1", cur->path, nil);
+			execl("/bin/play", "play", "-o", "/fd/1", path, nil);
 		}
 		close(0);
 		close(1);
@@ -728,8 +749,8 @@
 	}
 	if(pid < 0)
 		sysfatal("rfork: %r");
-	if(fd >= 0)
-		close(fd);
+	/* fd is q[1] when it's Icy */
+	close(fd);
 	close(p[0]);
 
 	c = 0;
@@ -767,7 +788,11 @@
 			}
 			break;
 		}
-
+		if(player->icytitlec != nil && nbrecv(player->icytitlec, &icytitle) != 0){
+			free(player->icytitle);
+			player->icytitle = icytitle;
+			redraw(1);
+		}
 		thiscover = nil;
 		if(player->img != nil && nbrecv(player->img, &thiscover) != 0){
 			freeimage(cover);
@@ -828,18 +853,24 @@
 	if(player->img != nil)
 		freeimage(recvp(player->img));
 freeplayer:
-	chanfree(player->ctl);
-	chanfree(player->ev);
+	close(q[0]);
+	close(p[1]);
 	if(pid >= 0)
 		postnote(PNGROUP, pid, "interrupt");
 	closeioproc(io);
-	if(p[1] >= 0)
-		close(p[1]);
+	if(player->icytitlec != nil){
+		while((icytitle = recvp(player->icytitlec)) != nil)
+			free(icytitle);
+		chanfree(player->icytitlec);
+	}
+	chanfree(player->ctl);
+	chanfree(player->ev);
 	if(player == playercurr)
 		playercurr = nil;
 	if(player == playernext)
 		playernext = nil;
 	free(buf);
+	free(player->icytitle);
 	free(player);
 	threadexits(nil);
 }
@@ -1488,6 +1519,7 @@
 			case 'q':
 			case Kdel:
 				stop(playercurr);
+				stop(playernext);
 				goto end;
 			case 'i':
 			case 'o':