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':