X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/22b9fa74de8e80471a5033ea067d3b360930b91d..91370169de7f552f3a54e5ef9e079a202a5efbfa:/server/play.c diff --git a/server/play.c b/server/play.c index 94567c4..d73aec7 100644 --- a/server/play.c +++ b/server/play.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder. - * Copyright (C) 2004, 2005, 2006 Richard Kettlewell + * Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ */ #include +#include "types.h" #include #include @@ -34,12 +35,16 @@ #include #include #include +#include +#include #include "event.h" #include "log.h" #include "mem.h" #include "configuration.h" #include "queue.h" +#include "server-queue.h" +#include "rights.h" #include "trackdb.h" #include "play.h" #include "plugin.h" @@ -85,17 +90,15 @@ static int speaker_terminated(ev_source attribute((unused)) *ev, int attribute((unused)) status, const struct rusage attribute((unused)) *rusage, void attribute((unused)) *u) { - if(status) - error(0, "speaker subprocess terminated with status %s", - wstat(status)); - return 0; + fatal(0, "speaker subprocess %s", + wstat(status)); } /* called when speaker process has something to say */ static int speaker_readable(ev_source *ev, int fd, void attribute((unused)) *u) { struct speaker_message sm; - int ret = speaker_recv(fd, &sm, 0); + int ret = speaker_recv(fd, &sm); if(ret < 0) return 0; /* EAGAIN */ if(!ret) { /* EOF */ @@ -113,6 +116,11 @@ static int speaker_readable(ev_source *ev, int fd, D(("SM_FINISHED %s", sm.id)); finished(ev); break; + case SM_UNKNOWN: + /* we asked for an unknown track to be cancelled */ + if(playing && !strcmp(sm.id, playing->id)) + finished(ev); + break; case SM_PLAYING: /* track ID is playing, DATA seconds played */ D(("SM_PLAYING %s %ld", sm.id, sm.data)); @@ -125,15 +133,12 @@ static int speaker_readable(ev_source *ev, int fd, } void speaker_setup(ev_source *ev) { - int sp[2], lfd; + int sp[2]; pid_t pid; + struct speaker_message sm; if(socketpair(PF_UNIX, SOCK_DGRAM, 0, sp) < 0) fatal(errno, "error calling socketpair"); - if(!isatty(2)) - lfd = logfd(ev, SPEAKER); - else - lfd = -1; if(!(pid = xfork())) { exitfn = _exit; ev_signal_atfork(ev); @@ -141,17 +146,17 @@ void speaker_setup(ev_source *ev) { xdup2(sp[0], 1); xclose(sp[0]); xclose(sp[1]); - if(lfd != -1) { - xdup2(lfd, 2); - xclose(lfd); - } signal(SIGPIPE, SIG_DFL); #if 0 execlp("valgrind", "valgrind", SPEAKER, "--config", configfile, - debugging ? "--debug" : "--no-debug", (char *)0); + debugging ? "--debug" : "--no-debug", + log_default == &log_syslog ? "--syslog" : "--no-syslog", + (char *)0); #else execlp(SPEAKER, SPEAKER, "--config", configfile, - debugging ? "--debug" : "--no-debug", (char *)0); + debugging ? "--debug" : "--no-debug", + log_default == &log_syslog ? "--syslog" : "--no-syslog", + (char *)0); #endif fatal(errno, "error invoking %s", SPEAKER); } @@ -159,9 +164,10 @@ void speaker_setup(ev_source *ev) { speaker_fd = sp[1]; xclose(sp[0]); cloexec(speaker_fd); - /* Don't need to make speaker_fd nonblocking because speaker_recv() uses - * MSG_DONTWAIT. */ - ev_fd(ev, ev_read, speaker_fd, speaker_readable, 0); + /* Wait for the speaker to be ready */ + speaker_recv(speaker_fd, &sm); + nonblock(speaker_fd); + ev_fd(ev, ev_read, speaker_fd, speaker_readable, 0, "speaker read"); } void speaker_reload(void) { @@ -169,7 +175,7 @@ void speaker_reload(void) { memset(&sm, 0, sizeof sm); sm.type = SM_RELOAD; - speaker_send(speaker_fd, &sm, -1); + speaker_send(speaker_fd, &sm); } /* timeout for play retry */ @@ -238,9 +244,9 @@ static int player_finished(ev_source *ev, switch(q->state) { case playing_unplayed: case playing_random: - /* If this was an SM_PREPARE track then either it failed or we deliberately - * stopped it because it was removed from the queue or moved down it. So - * leave it state alone for future use. */ + /* If this was a pre-prepared track then either it failed or we + * deliberately stopped it because it was removed from the queue or moved + * down it. So leave it state alone for future use. */ break; default: /* We actually started playing this track. */ @@ -281,17 +287,22 @@ static int find_player(const struct queue_entry *q) { } /* Return values from start() */ -#define START_OK 0 /* Succeeded. */ -#define START_HARDFAIL 1 /* Track is broken. */ -#define START_SOFTFAIL 2 /* Track OK, system (temporarily?) broken */ - -/* Play or prepare Q */ +#define START_OK 0 /**< @brief Succeeded. */ +#define START_HARDFAIL 1 /**< @brief Track is broken. */ +#define START_SOFTFAIL 2 /**< @brief Track OK, system (temporarily?) broken */ + +/** @brief Play or prepare @p q + * @param ev Event loop + * @param q Track to play/prepare + * @param prepare_only If true, only prepares track + * @return @ref START_OK, @ref START_HARDFAIL or @ref START_SOFTFAIL + */ static int start(ev_source *ev, struct queue_entry *q, - int smop) { + int prepare_only) { int n, lfd; const char *p; - int sp[2]; + int np[2], sfd; struct speaker_message sm; char buffer[64]; int optc; @@ -301,17 +312,22 @@ static int start(ev_source *ev, struct timespec ts; const char *waitdevice = 0; const char *const *optv; - pid_t pid; + pid_t pid, npid; + struct sockaddr_un addr; + uint32_t l; memset(&sm, 0, sizeof sm); - if(find_player_pid(q->id) > 0) { - if(smop == SM_PREPARE) return START_OK; - /* We have already sent an SM_PREPARE for this track so we just need to - * tell the speaker process to start actually playing the queued up audio - * data */ - strcpy(sm.id, q->id); - sm.type = SM_PLAY; - speaker_send(speaker_fd, &sm, -1); + D(("start %s %d", q->id, prepare_only)); + if(q->prepared) { + /* The track is alraedy prepared */ + if(!prepare_only) { + /* We want to run it, since it's prepared the answer is to tell the + * speaker to set it off */ + strcpy(sm.id, q->id); + sm.type = SM_PLAY; + speaker_send(speaker_fd, &sm); + D(("sent SM_PLAY for %s", sm.id)); + } return START_OK; } /* Find the player plugin. */ @@ -320,7 +336,7 @@ static int start(ev_source *ev, return START_HARDFAIL; q->type = play_get_type(q->pl); /* Can't prepare non-raw tracks. */ - if(smop == SM_PREPARE + if(prepare_only && (q->type & DISORDER_PLAYER_TYPEMASK) != DISORDER_PLAYER_RAW) return START_OK; /* Call the prefork function. */ @@ -332,8 +348,11 @@ static int start(ev_source *ev, } /* Use the second arg as the tag if available (it's probably a command name), * otherwise the module name. */ - lfd = logfd(ev, (config->player.s[n].s[2] - ? config->player.s[n].s[2] : config->player.s[n].s[1])); + if(!isatty(2)) + lfd = logfd(ev, (config->player.s[n].s[2] + ? config->player.s[n].s[2] : config->player.s[n].s[1])); + else + lfd = -1; optc = config->player.s[n].n - 2; optv = (void *)&config->player.s[n].s[2]; while(optc > 0 && optv[0][0] == '-') { @@ -360,26 +379,77 @@ static int start(ev_source *ev, exitfn = _exit; ev_signal_atfork(ev); signal(SIGPIPE, SIG_DFL); - xdup2(lfd, 1); - xdup2(lfd, 2); - xclose(lfd); /* tidy up */ + if(lfd != -1) { + xdup2(lfd, 1); + xdup2(lfd, 2); + xclose(lfd); /* tidy up */ + } setpgid(0, 0); if((q->type & DISORDER_PLAYER_TYPEMASK) == DISORDER_PLAYER_RAW) { - /* Raw format players write down a pipe (in fact a socket) to - * the speaker process. */ - sm.type = smop; - strcpy(sm.id, q->id); - if(socketpair(PF_UNIX, SOCK_STREAM, 0, sp) < 0) + /* "Raw" format players always have their output send down a pipe + * to the disorder-normalize process. This will connect to the + * speaker process to actually play the audio data. + */ + /* np will be the pipe to disorder-normalize */ + if(socketpair(PF_UNIX, SOCK_STREAM, 0, np) < 0) fatal(errno, "error calling socketpair"); - xshutdown(sp[0], SHUT_WR); - xshutdown(sp[1], SHUT_RD); - speaker_send(speaker_fd, &sm, sp[0]); + xshutdown(np[0], SHUT_WR); /* normalize reads from np[0] */ + xshutdown(np[1], SHUT_RD); /* decoder writes to np[1] */ + blocking(np[0]); + blocking(np[1]); + /* Start disorder-normalize */ + if(!(npid = xfork())) { + if(!xfork()) { + /* Connect to the speaker process */ + memset(&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof addr.sun_path, + "%s/speaker/socket", config->home); + sfd = xsocket(PF_UNIX, SOCK_STREAM, 0); + if(connect(sfd, (const struct sockaddr *)&addr, sizeof addr) < 0) + fatal(errno, "connecting to %s", addr.sun_path); + l = strlen(q->id); + if(write(sfd, &l, sizeof l) < 0 + || write(sfd, q->id, l) < 0) + fatal(errno, "writing to %s", addr.sun_path); + /* Await the ack */ + read(sfd, &l, 1); + /* Plumbing */ + xdup2(np[0], 0); + xdup2(sfd, 1); + xclose(np[0]); + xclose(np[1]); + xclose(sfd); + /* Ask the speaker to actually start playing the track; we do it here + * so it's definitely after ack. */ + if(!prepare_only) { + strcpy(sm.id, q->id); + sm.type = SM_PLAY; + speaker_send(speaker_fd, &sm); + D(("sent SM_PLAY for %s", sm.id)); + } + /* TODO stderr shouldn't be redirected for disorder-normalize + * (but it should be for play_track() */ + execlp("disorder-normalize", "disorder-normalize", + log_default == &log_syslog ? "--syslog" : "--no-syslog", + "--config", configfile, + (char *)0); + fatal(errno, "executing disorder-normalize"); + /* end of the innermost fork */ + } + _exit(0); + /* end of the middle fork */ + } + /* Wait for the middle fork to finish */ + while(waitpid(npid, &n, 0) < 0 && errno == EINTR) + ; /* Pass the file descriptor to the driver in an environment * variable. */ - snprintf(buffer, sizeof buffer, "DISORDER_RAW_FD=%d", sp[1]); + snprintf(buffer, sizeof buffer, "DISORDER_RAW_FD=%d", np[1]); if(putenv(buffer) < 0) fatal(errno, "error calling putenv"); - xclose(sp[0]); + /* Close all the FDs we don't need */ + xclose(np[0]); } if(waitdevice) { ao_initialize(); @@ -412,11 +482,14 @@ static int start(ev_source *ev, error(errno, "error calling fork"); if(q->type & DISORDER_PLAYER_PREFORK) play_cleanup(q->pl, q->data); /* else would leak */ - xclose(lfd); + if(lfd != -1) + xclose(lfd); return START_SOFTFAIL; } store_player_pid(q->id, pid); - xclose(lfd); + q->prepared = 1; + if(lfd != -1) + xclose(lfd); setpgid(pid, pid); ev_child(ev, pid, 0, player_finished, q); D(("player subprocess ID %lu", (unsigned long)pid)); @@ -434,7 +507,7 @@ int prepare(ev_source *ev, q->type = play_get_type(q->pl); if((q->type & DISORDER_PLAYER_TYPEMASK) != DISORDER_PLAYER_RAW) return 0; /* Not a raw player */ - return start(ev, q, SM_PREPARE); /* Prepare it */ + return start(ev, q, 1/*prepare_only*/); /* Prepare it */ } void abandon(ev_source attribute((unused)) *ev, @@ -452,7 +525,7 @@ void abandon(ev_source attribute((unused)) *ev, memset(&sm, 0, sizeof sm); sm.type = SM_CANCEL; strcpy(sm.id, q->id); - speaker_send(speaker_fd, &sm, -1); + speaker_send(speaker_fd, &sm); } int add_random_track(void) { @@ -510,7 +583,7 @@ void play(ev_source *ev) { return; D(("taken %p (%s) from queue", (void *)q, q->track)); /* Try to start playing. */ - switch(start(ev, q, SM_PLAY)) { + switch(start(ev, q, 0/*!prepare_only*/)) { case START_HARDFAIL: if(q == qhead.next) { queue_remove(q, 0); /* Abandon this track. */ @@ -613,7 +686,7 @@ void scratch(const char *who, const char *id) { memset(&sm, 0, sizeof sm); sm.type = SM_CANCEL; strcpy(sm.id, playing->id); - speaker_send(speaker_fd, &sm, -1); + speaker_send(speaker_fd, &sm); D(("sending SM_CANCEL for %s", playing->id)); } /* put a scratch track onto the front of the queue (but don't @@ -681,7 +754,7 @@ int pause_playing(const char *who) { case DISORDER_PLAYER_RAW: memset(&sm, 0, sizeof sm); sm.type = SM_PAUSE; - speaker_send(speaker_fd, &sm, -1); + speaker_send(speaker_fd, &sm); break; } if(who) info("paused by %s", who); @@ -712,7 +785,7 @@ void resume_playing(const char *who) { case DISORDER_PLAYER_RAW: memset(&sm, 0, sizeof sm); sm.type = SM_RESUME; - speaker_send(speaker_fd, &sm, -1); + speaker_send(speaker_fd, &sm); break; } if(who) info("resumed by %s", who);