Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/sys/src/cmd/oplayer.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <thread.h>
#include <omero.h>
#include <plumb.h>
#include <error.h>
#include <b.h>


typedef struct Player	Player;
typedef struct Evh	Evh;

enum {
	KF=	0xF000,	/* Rune: beginning of private Unicode space */
	Spec=	0xF800,
	Kup=	KF|0x0E,
	Kdown=	Spec|0x00,
};

struct Player{
	char*	dir;
	char**	songs;
	int	nsongs;
	int	active;
	int	pause;
	int	pid;	// of player process

	Panel*	gtag;
	Panel*	gsongcol;
	Panel*	gsongs;
	Panel*	gpause;
};

struct Evh {
	char*	name;
	void	(*f)(Player*, Oev*);
};


Player p;

static char playbuf[4*1024];
static Channel*	plumbc;

void update(Player*);

void
omerogone(void)
{
	sysfatal("terminated");
}

static void
playproc(void *a)
{
	int	fd, afd;
	int	n, wn;
	int	playing;
	Player*	p = a;

	threadsetname("playproc");
again:
	if (p->active < 0 || p->active >= p->nsongs)
		threadexits("bug");
	fd = open(p->songs[p->active], OREAD);
	afd= open("/devs/audio/audio", OWRITE);
	if (fd < 0 || afd < 0){
		close(fd);
		close(afd);
		threadexits("open");
	}
	playing = p->active;
	do{
		if (p->pause){
			while(p->pause)
				sleep(100);
			if (playing != p->active){
				wn = -1;
				n = 0;
				break;
			}
		}
		wn = n = read(fd, playbuf, sizeof(playbuf));
		if (n > 0)
			wn = write(afd, playbuf, n);
	} while(n  > 0 && wn == n && playing == p->active);
	close(fd);
	close(afd);
	if (n < 0 || n != wn)
		threadexits("io");
	if (playing == p->active){
		p->active++;
		if (p->active == p->nsongs)
			p->active = 0;
		update(p);
		goto again;
	}
	threadexits(nil);
}

void
play(Player* p)
{
	proccreate(playproc, p, 16*1024);
}

void
stop(Player* p)
{
	p->active = -1;
}

static void
plumbimg(char* dir, char* arg)
{
	Plumbmsg*m;
	static int	plumbsendfd = -1;

	if (plumbsendfd < 0)
		plumbsendfd = open("/mnt/plumb/send", OWRITE|OCEXEC);
	if (plumbsendfd < 0)
		return;
	m = malloc(sizeof(Plumbmsg));
	if (m == nil)
		return;
	m->src = strdup(argv0);
	m->dst = strdup("poster");
	m->wdir= strdup(dir);
	m->type = strdup("text");
	m->attr = nil;
	m->data = smprint("%s/%s", dir, arg);
	m->ndata= -1;
	assert(m->wdir && m->src && m->data);
	plumbsend(plumbsendfd, m);
	plumbfree(m);
}

static int
mayplumbimg(char* dir, char* f)
{
	int	n;

	n = strlen(f);
	if (n < 5)
		return 0;
	n -= 4;
	if (!cistrcmp(f+n, ".jpg") || !strcmp(f+n, ".gif") || !strcmp(f+n, ".img") || !strcmp(f+n, ".png")){
		plumbimg(dir, f);
		return 1;
	}
	return 0;
}

int
readsongs(Player* p, char* dir)
{
	Dir*	ents;
	int	fd;
	int	i;
	char	buf[512];
	int	hasfiles;

	if (chdir(dir) < 0)
		return -1;
	getwd(buf, sizeof(buf));
	fd = open(".", OREAD);
	if (fd < 0)
		return -1;
	free(p->dir);
	p->dir = estrdup(buf);
	for (i = 0; i < p->nsongs; i++)
		free(p->songs[i]);
	free(p->songs);
	p->songs = 0;
	p->nsongs = dirreadall(fd, &ents);
	close(fd);
	if (p->nsongs < 0){
		return -1;
	}
	p->songs = emalloc(p->nsongs * sizeof(char*));
	hasfiles = 0;
	for (i = 0; i < p->nsongs; i++)
		if (ents[i].qid.type&QTDIR)
			p->songs[i] = smprint("%s/", ents[i].name);
		else {
			if (!mayplumbimg(p->dir, ents[i].name)){
				hasfiles++;
				p->songs[i] = estrdup(ents[i].name);
			};
		}
	free(ents);
	p->active = -1;
	p->pause = 0;
	if (hasfiles)
		evhistory("oplayer", "look", p->dir);
	return hasfiles;
}

void
controls(Panel* w)
{
	Panel*	c;

	createsubpanel(w, "button:Play");
	createsubpanel(w, "button:Stop");
	createsubpanel(w, "button:Next");
	createsubpanel(w, "button:Done");
	p.gpause = createsubpanel(w, "button:Pause");
	c = createsubpanel(w, "slider:volume");
	openpanel(c, OWRITE);
	writepanel(c, "75", 2);
	closepanel(c);
	openpanelctl(c);
	panelctl(c, "tag");
	closepanelctl(c);
}


void
showlist(Player* p)
{
	int	i;
	char*	s;
	char*	txt;
	char*	mrk;
	char*	ap;

	txt = emalloc(32*1024);
	s = ap = txt;
	*txt = 0;
	for (i = 0; i < p->nsongs; i++){
		if (i != p->active)
			mrk = "";
		else {
			ap = s;
			mrk = "->";
		}
		s = seprint(s, txt+(32*1024), "%s\t%s\n", mrk, p->songs[i]);
	}
	if (!txt[0])
		strcpy(txt, "no songs.");
	openpanel(p->gsongs, OWRITE|OTRUNC);
	writepanel(p->gsongs, txt, strlen(txt));
	closepanel(p->gsongs);
	openpanelctl(p->gsongs);
	panelctl(p->gsongs, "sel %d %d\n", ap - txt, ap - txt);
	closepanelctl(p->gsongs);
	free(txt);
}

void
ui(Panel* w)
{
	Panel*	c;

	p.gtag = createsubpanel(w, "tag:player");
	c = createsubpanel(w, "row:controls");
	controls(c);
	p.gsongs = createsubpanel(w, "text:songs");
}

void
update(Player* p)
{
	char*	tag;

	tag = smprint("player — %s ..", p->dir ? p->dir : "no dir");
	openpanel(p->gtag, OWRITE|OTRUNC);
	writepanel(p->gtag, tag, strlen(tag));
	closepanel(p->gtag);
	free(tag);
	showlist(p);
}


int
isdir(char* p)
{
	Dir*	d;
	int	r;

	d = dirstat(p);
	if (d == nil)
		return 0;
	r = (d->qid.type&QTDIR);
	free(d);
	return r;
}

static int
findsong(Player* p, char* s)
{
	int	i;

	for (i = 0; i < p->nsongs; i++)
		if (!strcmp(p->songs[i], s))
			return i;
	// no exact match, try substring
	for (i = 0; i < p->nsongs; i++)
		if (strstr(p->songs[i], s))
			return i;

	return 0;
}

void
playfile(Player* p, char* file)
{
	char*	d;

	if (isdir(file)){
		if (readsongs(p, file) > 0){
			p->active = p->pause = 0;
			play(p);
		}
		update(p);
	} else if (access(file, AREAD) != -1){
		d = utfrrune(file, '/');
		if (d)
			*d++ = 0;
		readsongs(p, d ? file : ".");
		if (d){
			p->active = findsong(p, d);
			p->pause = 0;
			play(p);
		}
		update(p);
	}
}

void
elook(Player* p, Oev* e)
{
	char	str[40];
	int	vol;

	if (strstr(e->path, "/tag:")){
		if (readsongs(p, e->arg + 11 + 1) != -1)
			update(p);
		return;
	}
	if (strstr(e->path, "/text:songs")){
		if (isdir(e->arg + 11 + 1))
			readsongs(p, e->arg + 11 + 1);
		else {
			p->active = findsong(p, e->arg + 11 + 1);
			play(p);
		}
		update(p);
	}
	if (strstr(e->path, "/button:Play")){
		p->active = 0;
		play(p);
		update(p);
		return;
	}
	if (strstr(e->path, "/button:Stop")){
		stop(p);
		update(p);
		return;
	}
	if (strstr(e->path, "/button:Done")){
		stop(p);
		omeroterm();
		threadexitsall(nil);
	}
	if (strstr(e->path, "/button:Pause")){
		openpanel(p->gpause, OWRITE|OTRUNC);
		if (p->pause){
			writepanel(p->gpause,"Pause", 5);
			p->pause = 0;
		} else {
			writepanel(p->gpause,"Resume", 6);
			p->pause++;
		}
		closepanel(p->gpause);
		return;
	}
	if (strstr(e->path, "/button:Next")){
		p->active++;
		if (p->active == p->nsongs)
			p->active = 0;
		update(p);
		play(p);
		return;
	}
	if (strstr(e->path, "/slider:volume")){
		vol = atoi(e->arg);
		seprint(str, str+sizeof(str), "audio out %d", vol);
		writefstr("/devs/audio/volume", str);
	}
}

void
eexit(Player* , Oev* )
{
	omeroterm();
	threadexitsall(nil);
}

Evh evsh[] = {
	{ "Look",	elook },
	{ "look",	elook },
	{ "exit",	eexit },
	{ "exec",	elook },	// BUG
	{ "data",	elook },	// BUG
};


void
threadmain(int argc, char* argv[])
{
	Panel*	w;
	Channel* ec;
	Oev	e;
	Plumbmsg* m;
	int	i, ai;
	Alt	a[] = {
		{ nil, &e, CHANRCV },
		{ nil, &m, CHANRCV },
		{ nil, nil,CHANEND }};

	ARGBEGIN{
	case 'd':
		omerodebug++;
		break;
	default:
		fprint(2, "usage: %s [-d] [dir]\n", argv0);
		sysfatal("usage");
	}ARGEND;
	if (argc > 1){
		fprint(2, "usage: %s [-d] [dir]\n", argv0);
		sysfatal("usage");
	}
	ec = omeroeventchan(nil);
	a[0].c = ec;
	w = createpanel("oplayer", "col", nil);
	if (w == nil)
		sysfatal("createpanel: %r\n");
	plumbc = createportproc("song");
	a[1].c = plumbc;

	readsongs(&p, argc ? argv[0] : "/n/music");

	ui(w);

	update(&p);
	closepanelctl(w);
	while((ai = alt(a)) != -1){
		switch(ai){
		case 0:
			for (i = 0; i < nelem(evsh); i++)
				if (!strcmp(evsh[i].name, e.ev)){
					evsh[i].f(&p, &e);
					break;
				}
			clearoev(&e);
			break;
		case 1:
			playfile(&p, m->data);
			plumbfree(m);
			break;
		}
	}
	eexit(&p, nil);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.