X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/0213d16c987958e5e94cc4774d18201a2a1c27d0..af7a85b709c60a644fe04e77a3289bcd120ba1b2:/server/play.c diff --git a/server/play.c b/server/play.c index b50e19b..f38c9d5 100644 --- a/server/play.c +++ b/server/play.c @@ -36,12 +36,15 @@ #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" @@ -95,7 +98,7 @@ static int speaker_terminated(ev_source attribute((unused)) *ev, 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 */ @@ -108,10 +111,11 @@ static int speaker_readable(ev_source *ev, int fd, D(("SM_PAUSED %s %ld", sm.id, sm.data)); playing->sofar = sm.data; break; - case SM_FINISHED: - /* the playing track finished */ - D(("SM_FINISHED %s", sm.id)); - finished(ev); + case SM_FINISHED: /* scratched the playing track */ + case SM_STILLBORN: /* scratched too early */ + case SM_UNKNOWN: /* scratched WAY too early */ + if(playing && !strcmp(sm.id, playing->id)) + finished(ev); break; case SM_PLAYING: /* track ID is playing, DATA seconds played */ @@ -125,15 +129,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 +142,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 +160,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 +171,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 +240,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 +283,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 np[2], sp[2]; + int np[2], sfd; struct speaker_message sm; char buffer[64]; int optc; @@ -302,16 +309,21 @@ static int start(ev_source *ev, const char *waitdevice = 0; const char *const *optv; 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 +332,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 +344,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] == '-') { @@ -358,59 +373,85 @@ static int start(ev_source *ev, switch(pid = fork()) { case 0: /* child */ exitfn = _exit; + progname = "disorderd-fork"; 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 need special treatment: - * 1) their output needs to go via the disorder-normalize process - * 2) the output of that needs to be passed to the disorder-speaker - * process. + /* "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(np[0], SHUT_WR); /* normalize reads from np[0] */ + /* Beware of the Leopard! On OS X 10.5.x, the order of the shutdown + * calls here DOES MATTER. If you do the SHUT_WR first then the SHUT_RD + * fails with "Socket is not connected". I think this is a bug but + * provided implementors either don't care about the order or all agree + * about the order, choosing the reliable order is an adequate + * workaround. */ xshutdown(np[1], SHUT_RD); /* decoder writes to np[1] */ - /* sp will be the pipe to disorder-speaker */ - sm.type = smop; - if(socketpair(PF_UNIX, SOCK_STREAM, 0, sp) < 0) - fatal(errno, "error calling socketpair"); - xshutdown(sp[0], SHUT_WR); /* speaker reads from sp[0] */ - xshutdown(sp[1], SHUT_RD); /* normalize writes to sp[1] */ + xshutdown(np[0], SHUT_WR); /* normalize reads from np[0] */ + 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(sp[1], 1); + xdup2(sfd, 1); xclose(np[0]); xclose(np[1]); - xclose(sp[0]); - xclose(sp[1]); - execlp("disorder-normalize", "disorder-normalize", (char *)0); + 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); - } else { - int w; - - while(waitpid(npid, &w, 0) < 0 && errno == EINTR) - ; + /* end of the middle fork */ } - /* Send the speaker process the file descriptor to read from */ - strcpy(sm.id, q->id); - speaker_send(speaker_fd, &sm, sp[0]); + /* 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", np[1]); if(putenv(buffer) < 0) fatal(errno, "error calling putenv"); /* Close all the FDs we don't need */ - xclose(sp[0]); - xclose(sp[1]); xclose(np[0]); } if(waitdevice) { @@ -444,11 +485,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)); @@ -466,7 +510,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, @@ -484,7 +528,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) { @@ -542,7 +586,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. */ @@ -645,7 +689,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 @@ -713,7 +757,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); @@ -744,7 +788,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);