+/** @brief Control thread
+ *
+ * This thread is responsible for accepting control commands from Disobedience
+ * (or other controllers) over an AF_UNIX stream socket with a path specified
+ * by the @c --socket option. The protocol uses simple string commands and
+ * replies:
+ *
+ * - @c stop will shut the player down
+ * - @c query will send back the reply @c running
+ * - anything else is ignored
+ *
+ * Commands and response strings terminated by shutting down the connection or
+ * by a newline. No attempt is made to multiplex multiple clients so it is
+ * important that the command be sent as soon as the connection is made - it is
+ * assumed that both parties to the protocol are entirely cooperating with one
+ * another.
+ */
+static void *control_thread(void attribute((unused)) *arg) {
+ struct sockaddr_un sa;
+ int sfd, cfd;
+ char *line;
+ socklen_t salen;
+ FILE *fp;
+
+ assert(control_socket);
+ unlink(control_socket);
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ strcpy(sa.sun_path, control_socket);
+ sfd = xsocket(PF_UNIX, SOCK_STREAM, 0);
+ if(bind(sfd, (const struct sockaddr *)&sa, sizeof sa) < 0)
+ fatal(errno, "error binding to %s", control_socket);
+ if(listen(sfd, 128) < 0)
+ fatal(errno, "error calling listen on %s", control_socket);
+ info("listening on %s", control_socket);
+ for(;;) {
+ salen = sizeof sa;
+ cfd = accept(sfd, (struct sockaddr *)&sa, &salen);
+ if(cfd < 0) {
+ switch(errno) {
+ case EINTR:
+ case EAGAIN:
+ break;
+ default:
+ fatal(errno, "error calling accept on %s", control_socket);
+ }
+ }
+ if(!(fp = fdopen(cfd, "r+"))) {
+ error(errno, "error calling fdopen for %s connection", control_socket);
+ close(cfd);
+ continue;
+ }
+ if(!inputline(control_socket, fp, &line, '\n')) {
+ if(!strcmp(line, "stop")) {
+ info("stopped via %s", control_socket);
+ exit(0); /* terminate immediately */
+ }
+ if(!strcmp(line, "query"))
+ fprintf(fp, "running");
+ xfree(line);
+ }
+ if(fclose(fp) < 0)
+ error(errno, "error closing %s connection", control_socket);
+ }
+}
+
+/** @brief Drop the first packet
+ *
+ * Assumes that @ref lock is held.
+ */
+static void drop_first_packet(void) {
+ if(pheap_count(&packets)) {
+ struct packet *const p = pheap_remove(&packets);
+ nsamples -= p->nsamples;
+ playrtp_free_packet(p);
+ pthread_cond_broadcast(&cond);
+ }
+}
+
+/** @brief Background thread adding packets to heap
+ *
+ * This just transfers packets from @ref received_packets to @ref packets. It
+ * is important that it holds @ref receive_lock for as little time as possible,
+ * in order to minimize the interval between calls to read() in
+ * listen_thread().
+ */
+static void *queue_thread(void attribute((unused)) *arg) {
+ struct packet *p;
+
+ for(;;) {
+ /* Get the next packet */
+ pthread_mutex_lock(&receive_lock);
+ while(!received_packets) {
+ pthread_cond_wait(&receive_cond, &receive_lock);
+ }
+ p = received_packets;
+ received_packets = p->next;
+ if(!received_packets)
+ received_tail = &received_packets;
+ --nreceived;
+ pthread_mutex_unlock(&receive_lock);
+ /* Add it to the heap */
+ pthread_mutex_lock(&lock);
+ pheap_insert(&packets, p);
+ nsamples += p->nsamples;
+ pthread_cond_broadcast(&cond);
+ pthread_mutex_unlock(&lock);
+ }