chiark / gitweb /
More reliable gapless play.
[disorder] / server / play.c
index 13854f7..93a7f48 100644 (file)
@@ -1,60 +1,26 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
+ * Copyright (C) 2004-2008 Richard Kettlewell
  *
- * This program is free software; you can redistribute it and/or modify
+ * 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
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+/** @file server/play.c
+ * @brief Playing tracks
  */
 
-#include <config.h>
-#include "types.h"
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fnmatch.h>
-#include <time.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <sys/socket.h>
-#include <string.h>
-#include <stdio.h>
-#include <pcre.h>
+#include "disorder-server.h"
 #include <ao/ao.h>
-#include <sys/wait.h>
-#include <sys/un.h>
-
-#include "event.h"
-#include "log.h"
-#include "mem.h"
-#include "configuration.h"
-#include "queue.h"
-#include "server-queue.h"
-#include "trackdb.h"
-#include "play.h"
-#include "plugin.h"
-#include "wstat.h"
-#include "eventlog.h"
-#include "logfd.h"
-#include "syscalls.h"
-#include "speaker-protocol.h"
-#include "disorder.h"
-#include "signame.h"
-#include "hash.h"
 
 #define SPEAKER "disorder-speaker"
 
@@ -110,10 +76,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 */
@@ -127,16 +94,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);
@@ -144,10 +107,6 @@ 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,
@@ -169,7 +128,8 @@ void speaker_setup(ev_source *ev) {
   /* 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");
+  if(ev_fd(ev, ev_read, speaker_fd, speaker_readable, 0, "speaker read") < 0)
+    fatal(0, "error registering speaker socket fd");
 }
 
 void speaker_reload(void) {
@@ -180,25 +140,6 @@ void speaker_reload(void) {
   speaker_send(speaker_fd, &sm);
 }
 
-/* timeout for play retry */
-static int play_again(ev_source *ev,
-                     const struct timeval attribute((unused)) *now,
-                     void attribute((unused)) *u) {
-  D(("play_again"));
-  play(ev);
-  return 0;
-}
-
-/* try calling play() again after @offset@ seconds */
-static void retry_play(ev_source *ev, int offset) {
-  struct timeval w;
-
-  D(("retry_play(%d)", offset));
-  gettimeofday(&w, 0);
-  w.tv_sec += offset;
-  ev_timeout(ev, 0, &w, play_again, 0);
-}
-
 /* Called when the currently playing track finishes playing.  This
  * might be because the player finished or because the speaker process
  * told us so. */
@@ -225,7 +166,10 @@ static void finished(ev_source *ev) {
   recent_write();
   forget_player_pid(playing->id);
   playing = 0;
-  if(ev) retry_play(ev, config->gap);
+  /* Try to play something else */
+  /* TODO re-support config->gap? */
+  if(ev)
+    play(ev);
 }
 
 /* Called when a player terminates. */
@@ -320,14 +264,16 @@ static int start(ev_source *ev,
 
   memset(&sm, 0, sizeof sm);
   D(("start %s %d", q->id, prepare_only));
-  if(find_player_pid(q->id) > 0) {
-    if(prepare_only) return START_OK;
-    /* We have already prepared 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);
-    D(("sent SM_PLAY for %s", sm.id));
+  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. */
@@ -377,6 +323,7 @@ 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);
     if(lfd != -1) {
@@ -393,8 +340,14 @@ static int start(ev_source *ev,
       /* 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] */
+      xshutdown(np[0], SHUT_WR);       /* normalize reads from np[0] */
       blocking(np[0]);
       blocking(np[1]);
       /* Start disorder-normalize */
@@ -404,7 +357,7 @@ static int start(ev_source *ev,
          memset(&addr, 0, sizeof addr);
          addr.sun_family = AF_UNIX;
          snprintf(addr.sun_path, sizeof addr.sun_path,
-                  "%s/speaker", config->home);
+                  "%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);
@@ -413,7 +366,8 @@ static int start(ev_source *ev,
             || write(sfd, q->id, l) < 0)
            fatal(errno, "writing to %s", addr.sun_path);
          /* Await the ack */
-         read(sfd, &l, 1);
+         if (read(sfd, &l, 1) < 0) 
+               fatal(errno, "reading ack from %s", addr.sun_path);
          /* Plumbing */
          xdup2(np[0], 0);
          xdup2(sfd, 1);
@@ -428,7 +382,12 @@ static int start(ev_source *ev,
            speaker_send(speaker_fd, &sm);
            D(("sent SM_PLAY for %s", sm.id));
          }
-         execlp("disorder-normalize", "disorder-normalize", (char *)0);
+         /* 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 */
        }
@@ -482,6 +441,7 @@ static int start(ev_source *ev,
     return START_SOFTFAIL;
   }
   store_player_pid(q->id, pid);
+  q->prepared = 1;
   if(lfd != -1)
     xclose(lfd);
   setpgid(pid, pid);
@@ -522,34 +482,40 @@ void abandon(ev_source attribute((unused)) *ev,
   speaker_send(speaker_fd, &sm);
 }
 
-int add_random_track(void) {
+/** @brief Called with a new random track
+ * @param ev Event loop
+ * @param track Track name
+ */
+static void chosen_random_track(ev_source *ev,
+                               const char *track) {
+  struct queue_entry *q;
+
+  if(!track)
+    return;
+  /* Add the track to the queue */
+  q = queue_add(track, 0, WHERE_END, origin_random);
+  D(("picked %p (%s) at random", (void *)q, q->track));
+  queue_write();
+  /* Maybe a track can now be played */
+  play(ev);
+}
+
+/** @brief Maybe add a randomly chosen track
+ * @param ev Event loop
+ */
+void add_random_track(ev_source *ev) {
   struct queue_entry *q;
-  const char *p;
   long qlen = 0;
-  int rc = 0;
 
   /* If random play is not enabled then do nothing. */
   if(shutting_down || !random_is_enabled())
-    return 0;
+    return;
   /* Count how big the queue is */
   for(q = qhead.next; q != &qhead; q = q->next)
     ++qlen;
-  /* Add random tracks until the queue is at the right size */
-  while(qlen < config->queue_pad) {
-    /* Try to pick a random track */
-    if(!(p = trackdb_random(16))) {
-      rc = -1;
-      break;
-    }
-    /* Add it to the end of the queue. */
-    q = queue_add(p, 0, WHERE_END);
-    q->state = playing_random;
-    D(("picked %p (%s) at random", (void *)q, q->track));
-    ++qlen;
-  }
-  /* Commit the queue */
-  queue_write();
-  return rc;
+  /* If it's smaller than the desired size then add a track */
+  if(qlen < config->queue_pad)
+    trackdb_request_random(ev, chosen_random_track);
 }
 
 /* try to play a track */
@@ -559,21 +525,20 @@ void play(ev_source *ev) {
 
   D(("play playing=%p", (void *)playing));
   if(shutting_down || playing || !playing_is_enabled()) return;
-  /* If the queue is empty then add a random track. */
+  /* See if there's anything to play */
   if(qhead.next == &qhead) {
-    if(!random_enabled)
-      return;
-    if(add_random_track()) {
-      /* On error, try again in 10s. */
-      retry_play(ev, 10);
-      return;
-    }
-    /* Now there must be at least one track in the queue. */
+    /* Queue is empty.  We could just wait around since there are periodic
+     * attempts to add a random track anyway.  However they are rarer than
+     * attempts to force a track so we initiate one now. */
+    add_random_track(ev);
+    return;
   }
+  /* There must be at least one track in the queue. */
   q = qhead.next;
-  /* If random play is disabled but the track is a random one then don't play
-   * it.  play() will be called again when random play is re-enabled. */
-  if(!random_enabled && q->state == playing_random)
+  /* If random play is disabled but the track is a non-adopted random one
+   * then don't play it.  play() will be called again when random play is
+   * re-enabled. */
+  if(!random_enabled && q->origin == origin_random)
     return;
   D(("taken %p (%s) from queue", (void *)q, q->track));
   /* Try to start playing. */
@@ -584,19 +549,11 @@ void play(ev_source *ev) {
       queue_played(q);
       recent_write();
     }
-    if(qhead.next == &qhead)
-      /* Queue is empty, wait a bit before trying something else (so we don't
-       * sit there looping madly in the presence of persistent problem).  Note
-       * that we might not reliably get a random track lookahead in this case,
-       * but if we get here then really there are bigger problems. */
-      retry_play(ev, 1);
-    else
-      /* More in queue, try again now. */
-      play(ev);
+    /* Oh well, try the next one */
+    play(ev);
     break;
   case START_SOFTFAIL:
-    /* Try same track again in a bit. */
-    retry_play(ev, 10);
+    /* We'll try the same track again shortly. */
     break;
   case START_OK:
     if(q == qhead.next) {
@@ -611,7 +568,7 @@ void play(ev_source *ev) {
             playing->submitter ? playing->submitter : (const char *)0,
             (const char *)0);
     /* Maybe add a random track. */
-    add_random_track();
+    add_random_track(ev);
     /* If there is another track in the queue prepare it now.  This could
      * potentially be a just-added random track. */
     if(qhead.next != &qhead)
@@ -629,7 +586,7 @@ int playing_is_enabled(void) {
 void enable_playing(const char *who, ev_source *ev) {
   trackdb_set_global("playing", "yes", who);
   /* Add a random track if necessary. */
-  add_random_track();
+  add_random_track(ev);
   play(ev);
 }
 
@@ -645,7 +602,7 @@ int random_is_enabled(void) {
 
 void enable_random(const char *who, ev_source *ev) {
   trackdb_set_global("random-play", "yes", who);
-  add_random_track();
+  add_random_track(ev);
   play(ev);
 }
 
@@ -687,8 +644,7 @@ void scratch(const char *who, const char *id) {
      * bother if playing is disabled) */
     if(playing_is_enabled() && config->scratch.n) {
       int r = rand() * (double)config->scratch.n / (RAND_MAX + 1.0);
-      q = queue_add(config->scratch.s[r], who, WHERE_START);
-      q->state = playing_isscratch;
+      q = queue_add(config->scratch.s[r], who, WHERE_START, origin_scratch);
     }
     notify_scratch(playing->track, playing->submitter, who,
                   time(0) - playing->played);