]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/resample'
authorAndre Noll <maan@systemlinux.org>
Sun, 16 Dec 2012 23:09:22 +0000 (00:09 +0100)
committerAndre Noll <maan@systemlinux.org>
Sun, 16 Dec 2012 23:09:22 +0000 (00:09 +0100)
Was cooking for quite some time. The merge conflicted slightly,
but this was easy to fix.

However, even with the conflict resolved, the merged tree would not
compile because after the merge para_play depends on libsamplerate,
but this dependency was not encoded in configure.ac.

There was no way to fix this issue in either of the two branches
involved without rewriting history: The recently merged t/afh_receiver
branch (which introduced para_play) had no idea of libsamplerate
while t/resample has no idea of para_play.

This merge commit fixes both issues.

0eb69b resample filter: Implementation.
cad284 resample filter: Infrastructure.
37e0df check_wav: Ask parent nodes before falling back to defaults.
216399 Replace check_wav_task by write_task.
1af65c Move wav detection code to a separate file.
d5dc1c write: Make wav-related config options modular.
c25d04 para_filter: Call proper ->free_config method on shutdown.

Conflicts:
m4/gengetopt/makefile

35 files changed:
Makefile.in
NEWS
afh.c
afh.h
afh_common.c
afh_recv.c [new file with mode: 0644]
afs.c
alsa_write.c
audiod.c
client_common.c
command.c
configure.ac
crypt.c
dccp_recv.c
error.h
file_write.c
http_recv.c
interactive.c
interactive.h
m4/gengetopt/afh.m4
m4/gengetopt/afh_recv.m4 [new file with mode: 0644]
m4/gengetopt/makefile
m4/gengetopt/play.m4 [new file with mode: 0644]
oss_write.c
osx_write.c
play.c [new file with mode: 0644]
play.cmd [new file with mode: 0644]
recv.c
recv.h
recv_common.c
sched.c
sched.h
server.c
udp_recv.c
web/manual.m4

index 3755e591404d543c54c72af7015c7317c27b6cea..4d1c23239b5787d5ff9d55ed8837d830bab02dda 100644 (file)
@@ -141,6 +141,10 @@ $(man_dir)/para_audiod.1: para_audiod audiod_command_list.man | $(man_dir)
        @[ -z "$(Q)" ] || echo 'MAN $<'
        $(Q) $(HELP2MAN) -h --detailed-help -N -i audiod_command_list.man ./para_audiod > $@
 
+$(man_dir)/para_play.1: para_play play_command_list.man | $(man_dir)
+       @[ -z "$(Q)" ] || echo 'MAN $<'
+       $(Q) $(HELP2MAN) -h --detailed-help -N -i play_command_list.man ./para_play > $@
+
 $(man_dir)/%.1: % | $(man_dir)
        @[ -z "$(Q)" ] || echo 'MAN $<'
        $(Q) $(HELP2MAN) -h --detailed-help -N ./$< > $@
@@ -219,7 +223,7 @@ $(dep_dir)/%.d: %.c | $(dep_dir)
 
 all_objs := @recv_objs@ @filter_objs@ @client_objs@ @gui_objs@ \
        @audiod_objs@ @audioc_objs@ @fade_objs@ @server_objs@ \
-       @write_objs@ @afh_objs@
+       @write_objs@ @afh_objs@ @play_objs@
 deps := $(addprefix $(dep_dir)/, $(all_objs:.o=.d))
 
 recv_objs := $(addprefix $(object_dir)/, @recv_objs@)
@@ -232,6 +236,7 @@ fade_objs := $(addprefix $(object_dir)/, @fade_objs@)
 server_objs := $(addprefix $(object_dir)/, @server_objs@)
 write_objs := $(addprefix $(object_dir)/, @write_objs@)
 afh_objs := $(addprefix $(object_dir)/, @afh_objs@)
+play_objs := $(addprefix $(object_dir)/, @play_objs@)
 
 ifeq ($(findstring clean, $(MAKECMDGOALS)),)
 -include $(deps)
@@ -277,6 +282,10 @@ para_afh: $(afh_objs)
        @[ -z "$(Q)" ] || echo 'LD $@'
        $(Q) $(CC) $(LDFLAGS) -o $@ $(afh_objs) @afh_ldflags@
 
+para_play: $(play_objs)
+       @[ -z "$(Q)" ] || echo 'LD $@'
+       $(Q) $(CC) $(LDFLAGS) -o $@ $(play_objs) @play_ldflags@
+
 clean:
        @[ -z "$(Q)" ] || echo 'CLEAN'
        $(Q) rm -f @executables@
diff --git a/NEWS b/NEWS
index 69372cc42c4ea40712fe6f5a337840875ddff04b..62e3b7d1b08bdef9ac0a3719cf719e803c27b91f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,7 +1,11 @@
----------------------------------------------
-0.?.? (to be announced) "volatile relativity"
----------------------------------------------
+----------------------------------------------
+0.4.12 (to be announced) "volatile relativity"
+----------------------------------------------
+The new command line player, ALSA support for para_fade, and the
+improved build system are the highlights of this release, which
+probably marks the end of the 0.4.x series.
 
+       - The afh receiver and the para_play executable
        - The "versions" directory has been removed from the master
          branch. The tarballs of the old releases are now available
          in the new "releases" branch.
diff --git a/afh.c b/afh.c
index 5b1cfe9bd1bd367939b9df2ddce2e4069c6df70d..4f774f19b846203d9000b56bb6d8e81e4a7b0f7d 100644 (file)
--- a/afh.c
+++ b/afh.c
@@ -25,41 +25,18 @@ INIT_STDERR_LOGGING(loglevel)
 
 static void print_info(int audio_format_num, struct afh_info *afhi)
 {
-       printf("%s: %dkbit/s\n" /* bitrate */
-               "%s: %s\n" /* format */
-               "%s: %dHz\n" /* frequency */
-               "%s: %d\n" /* channels */
-               "%s: %lu\n" /* seconds total */
-               "%s: %lu: %lu\n" /* chunk time */
-               "%s: %lu\n" /* num chunks */
-               "%s: %s\n" /* techinfo */
-               "%s: %s\n" /* artist */
-               "%s: %s\n" /* title */
-               "%s: %s\n" /* year */
-               "%s: %s\n" /* album */
-               "%s: %s\n", /* comment */
-               status_item_list[SI_BITRATE], afhi->bitrate,
-               status_item_list[SI_FORMAT], audio_format_name(audio_format_num),
-               status_item_list[SI_FREQUENCY], afhi->frequency,
-               status_item_list[SI_CHANNELS], afhi->channels,
-               status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total,
-               status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec,
-                       (long unsigned)afhi->chunk_tv.tv_usec,
-               status_item_list[SI_NUM_CHUNKS], afhi->chunks_total,
-               status_item_list[SI_TECHINFO], afhi->techinfo? afhi->techinfo : "",
-               status_item_list[SI_ARTIST], afhi->tags.artist? afhi->tags.artist : "",
-               status_item_list[SI_TITLE], afhi->tags.title? afhi->tags.title : "",
-               status_item_list[SI_YEAR], afhi->tags.year? afhi->tags.year : "",
-               status_item_list[SI_ALBUM], afhi->tags.album? afhi->tags.album : "",
-               status_item_list[SI_COMMENT], afhi->tags.comment? afhi->tags.comment : ""
-       );
+       char *msg;
+
+       afh_get_afhi_txt(audio_format_num, afhi, &msg);
+       printf("%s", msg);
+       free(msg);
 }
 
 static void print_chunk_table(struct afh_info *afhi)
 {
        int i;
 
-       if (!conf.human_given) {
+       if (conf.parser_friendly_given) {
                printf("chunk_table: ");
                for (i = 0; i <= afhi->chunks_total; i++)
                        printf("%u ", afhi->chunk_table[i]);
@@ -80,82 +57,6 @@ static void print_chunk_table(struct afh_info *afhi)
        }
 }
 
-static int cat_file(struct afh_info *afhi, int audio_format_id,
-               void *audio_file_data, size_t audio_file_size)
-{
-       int ret;
-       struct timeval stream_start;
-       long unsigned i, first_chunk, last_chunk;
-       const char *buf;
-       char *header;
-       size_t size;
-
-       if (conf.begin_chunk_arg < 0) {
-               if (-conf.begin_chunk_arg > afhi->chunks_total)
-                       return -ERRNO_TO_PARA_ERROR(EINVAL);
-               first_chunk = afhi->chunks_total + conf.begin_chunk_arg;
-       } else
-               first_chunk = conf.begin_chunk_arg;
-       if (conf.end_chunk_given) {
-               if (conf.end_chunk_arg < 0) {
-                       if (-conf.end_chunk_arg > afhi->chunks_total)
-                               return -ERRNO_TO_PARA_ERROR(EINVAL);
-                       last_chunk = afhi->chunks_total + conf.end_chunk_arg;
-               } else {
-                       if (conf.end_chunk_arg >= afhi->chunks_total)
-                               return -ERRNO_TO_PARA_ERROR(EINVAL);
-                       last_chunk = conf.end_chunk_arg;
-               }
-       } else
-               last_chunk = afhi->chunks_total - 1;
-       if (first_chunk >= last_chunk)
-               return -ERRNO_TO_PARA_ERROR(EINVAL);
-       if (!afhi->chunks_total)
-               return 1;
-       /* eliminate the possibility of short writes */
-       ret = mark_fd_blocking(STDOUT_FILENO);
-       if (ret < 0)
-               return ret;
-       if (first_chunk > 0 && !conf.no_header_given) {
-               afh_get_header(afhi, audio_format_id, audio_file_data, audio_file_size,
-                       &header, &size);
-               if (size > 0) {
-                       PARA_INFO_LOG("writing header (%zu bytes)\n", size);
-                       ret = write_all(STDOUT_FILENO, header, size);
-                       afh_free_header(header, audio_format_id);
-                       if (ret < 0)
-                               return ret;
-                       if (ret != size)
-                               return -E_AFH_SHORT_WRITE;
-               }
-       }
-       PARA_NOTICE_LOG("writing chunks %lu - %lu\n", first_chunk, last_chunk);
-       gettimeofday(&stream_start, NULL);
-       for (i = first_chunk; i <= last_chunk; i++) {
-               struct timeval now, diff, next_chunk;
-               afh_get_chunk(i, afhi, audio_file_data, &buf, &size);
-               PARA_DEBUG_LOG("chunk %lu: size %zu\n", i, size);
-               if (conf.just_in_time_given) {
-                       compute_chunk_time(i - first_chunk, &afhi->chunk_tv,
-                               &stream_start, &next_chunk);
-                       gettimeofday(&now, NULL);
-                       ret = tv_diff(&next_chunk, &now, &diff);
-                       if (ret > 0) {
-                               ret = para_select(1, NULL, NULL, &diff);
-                               if (ret < 0)
-                                       return ret;
-                       }
-               }
-               if (!size)
-                       continue;
-               PARA_INFO_LOG("writing chunk %lu\n", i);
-               ret = write_all(STDOUT_FILENO, buf, size);
-               if (ret < 0)
-                       return ret;
-       }
-       return 1;
-}
-
 /**
  * The main function of para_afh.
  *
@@ -177,8 +78,6 @@ int main(int argc, char **argv)
        ret = -E_AFH_SYNTAX;
        if (conf.inputs_num == 0)
                goto out;
-       if (conf.stream_given && conf.inputs_num != 1)
-               goto out;
        afh_init();
        for (i = 0; i < conf.inputs_num; i++) {
                int ret2;
@@ -194,16 +93,11 @@ int main(int argc, char **argv)
                        goto out;
 
                audio_format_num = ret;
-               if (conf.stream_given)
-                       ret = cat_file(&afhi, audio_format_num,
-                               audio_file_data, audio_file_size);
-               else {
-                       printf("File %d: %s\n", i + 1, conf.inputs[i]);
-                       print_info(audio_format_num, &afhi);
-                       if (conf.chunk_table_given)
-                               print_chunk_table(&afhi);
-                       printf("\n");
-               }
+               printf("File %d: %s\n", i + 1, conf.inputs[i]);
+               print_info(audio_format_num, &afhi);
+               if (conf.chunk_table_given)
+                       print_chunk_table(&afhi);
+               printf("\n");
                clear_afhi(&afhi);
                ret2 = para_munmap(audio_file_data, audio_file_size);
                if (ret2 < 0 && ret >= 0)
diff --git a/afh.h b/afh.h
index 7952809436752f19a795362469291a4996f0d468..4f23a173164a5678c66c4fddc43409f5851bdcbc 100644 (file)
--- a/afh.h
+++ b/afh.h
@@ -105,3 +105,4 @@ void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
                void *map, size_t mapsize, char **buf, size_t *len);
 void afh_free_header(char *header_buf, uint8_t audio_format_id);
 void clear_afhi(struct afh_info *afhi);
+unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result);
index 5b6301b5f3d06405930df8fbcc64c7095f7ece06..577053d45c743d01cc50494e5c9e84e74c42b6a2 100644 (file)
@@ -329,3 +329,47 @@ void afh_free_header(char *header_buf, uint8_t audio_format_id)
        if (afh->get_header)
                free(header_buf);
 }
+
+/**
+ * Pretty-print the contents of a struct afh_info into a buffer.
+ *
+ * \param audio_format_num The audio format number.
+ * \param afhi Pointer to the structure that contains the information.
+ * \param result Pretty-printed ahfi is here after the call.
+ *
+ * The \a result buffer is dynamically allocated and should be freed by the
+ * caller.
+ *
+ * \return The number of bytes. This function never fails.
+ */
+unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result)
+{
+       return xasprintf(result, "%s: %dkbit/s\n" /* bitrate */
+               "%s: %s\n" /* format */
+               "%s: %dHz\n" /* frequency */
+               "%s: %d\n" /* channels */
+               "%s: %lu\n" /* seconds total */
+               "%s: %lu: %lu\n" /* chunk time */
+               "%s: %lu\n" /* num chunks */
+               "%s: %s\n" /* techinfo */
+               "%s: %s\n" /* artist */
+               "%s: %s\n" /* title */
+               "%s: %s\n" /* year */
+               "%s: %s\n" /* album */
+               "%s: %s\n", /* comment */
+               status_item_list[SI_BITRATE], afhi->bitrate,
+               status_item_list[SI_FORMAT], audio_format_name(audio_format_num),
+               status_item_list[SI_FREQUENCY], afhi->frequency,
+               status_item_list[SI_CHANNELS], afhi->channels,
+               status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total,
+               status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec,
+                       (long unsigned)afhi->chunk_tv.tv_usec,
+               status_item_list[SI_NUM_CHUNKS], afhi->chunks_total,
+               status_item_list[SI_TECHINFO], afhi->techinfo? afhi->techinfo : "",
+               status_item_list[SI_ARTIST], afhi->tags.artist? afhi->tags.artist : "",
+               status_item_list[SI_TITLE], afhi->tags.title? afhi->tags.title : "",
+               status_item_list[SI_YEAR], afhi->tags.year? afhi->tags.year : "",
+               status_item_list[SI_ALBUM], afhi->tags.album? afhi->tags.album : "",
+               status_item_list[SI_COMMENT], afhi->tags.comment? afhi->tags.comment : ""
+       );
+}
diff --git a/afh_recv.c b/afh_recv.c
new file mode 100644 (file)
index 0000000..d042398
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2011-2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file afh_recv.c Receiver for streaming local files. */
+
+#include <regex.h>
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include "para.h"
+#include "error.h"
+#include "list.h"
+#include "sched.h"
+#include "ggo.h"
+#include "buffer_tree.h"
+#include "recv.h"
+#include "afh_recv.cmdline.h"
+#include "string.h"
+#include "fd.h"
+#include "afh.h"
+
+struct private_afh_recv_data {
+       int fd;
+       void *map;
+       size_t map_size;
+       struct afh_info afhi;
+       int audio_format_num;
+       long unsigned first_chunk;
+       long unsigned last_chunk;
+       struct timeval stream_start;
+       uint32_t current_chunk;
+};
+
+static int afh_execute(struct btr_node *btrn, const char *cmd, char **result)
+{
+       struct receiver_node *rn = btr_context(btrn);
+       struct private_afh_recv_data *pard = rn->private_data;
+
+       *result = NULL;
+       if (!strcmp(cmd, "seconds_total")) {
+               *result = make_message("%lu", pard->afhi.seconds_total);
+               return 1;
+       }
+       if (!strcmp(cmd, "chunks_total")) {
+               *result = make_message("%lu", pard->afhi.chunks_total);
+               return 1;
+       }
+       if (!strcmp(cmd, "afhi")) {
+               afh_get_afhi_txt(pard->audio_format_num, &pard->afhi, result);
+               return 1;
+       }
+       if (!strncmp(cmd, "repos", 5)) {
+               int32_t x;
+               int ret = para_atoi32(cmd + 5, &x);
+               if (ret < 0)
+                       return ret;
+               if (x >= pard->afhi.chunks_total)
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               pard->first_chunk = pard->current_chunk = x;
+               rn->task.error = 0;
+               return 1;
+       }
+       return -E_BTR_NAVAIL;
+}
+
+static void *afh_recv_parse_config(int argc, char **argv)
+{
+       struct afh_recv_args_info *tmp = para_calloc(sizeof(*tmp));
+
+       if (!afh_recv_cmdline_parser(argc, argv, tmp))
+               return tmp;
+       free(tmp);
+       return NULL;
+}
+
+static void afh_recv_free_config(void *conf)
+{
+       if (!conf)
+               return;
+       afh_recv_cmdline_parser_free(conf);
+       free(conf);
+}
+
+static int afh_recv_open(struct receiver_node *rn)
+{
+       struct afh_recv_args_info *conf = rn->conf;
+       struct private_afh_recv_data *pard;
+       struct afh_info *afhi;
+       char *filename = conf->filename_arg;
+
+       int ret;
+
+       if (!filename || *filename == '\0')
+               return -E_AFH_RECV_BAD_FILENAME;
+       rn->private_data = pard = para_calloc(sizeof(*pard));
+       afhi = &pard->afhi;
+       ret = mmap_full_file(filename, O_RDONLY, &pard->map,
+               &pard->map_size, &pard->fd);
+       if (ret < 0)
+               goto out;
+       ret = compute_afhi(filename, pard->map, pard->map_size,
+               pard->fd, afhi);
+       if (ret < 0)
+               goto out_unmap;
+       pard->audio_format_num = ret;
+       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+       if (afhi->chunks_total == 0)
+               goto out_clear_afhi;
+       if (PARA_ABS(conf->begin_chunk_arg) >= afhi->chunks_total)
+               goto out_clear_afhi;
+       if (conf->begin_chunk_arg >= 0)
+               pard->first_chunk = conf->begin_chunk_arg;
+       else
+               pard->first_chunk = afhi->chunks_total + conf->begin_chunk_arg;
+       if (conf->end_chunk_given) {
+               ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+               if (PARA_ABS(conf->end_chunk_arg) > afhi->chunks_total)
+                       goto out_clear_afhi;
+               if (conf->end_chunk_arg >= 0)
+                       pard->last_chunk = conf->end_chunk_arg;
+               else
+                       pard->last_chunk = afhi->chunks_total + conf->end_chunk_arg;
+       } else
+               pard->last_chunk = afhi->chunks_total - 1;
+       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+       if (pard->first_chunk >= pard->last_chunk)
+               goto out_clear_afhi;
+       pard->current_chunk = pard->first_chunk;
+       return pard->audio_format_num;
+out_clear_afhi:
+       clear_afhi(afhi);
+out_unmap:
+       para_munmap(pard->map, pard->map_size);
+       close(pard->fd);
+out:
+       freep(&rn->private_data);
+       return ret;
+}
+
+static void afh_recv_close(struct receiver_node *rn)
+{
+       struct private_afh_recv_data *pard;
+
+       if (!rn || !rn->private_data)
+               return;
+       pard = rn->private_data;
+       clear_afhi(&pard->afhi);
+       para_munmap(pard->map, pard->map_size);
+       close(pard->fd);
+       freep(&rn->private_data);
+}
+
+static void afh_recv_pre_select(struct sched *s, struct task *t)
+{
+       struct receiver_node *rn = container_of(t, struct receiver_node, task);
+       struct private_afh_recv_data *pard = rn->private_data;
+       struct afh_info *afhi = &pard->afhi;
+       struct afh_recv_args_info *conf = rn->conf;
+       struct timeval chunk_time;
+       int state = generic_recv_pre_select(s, t);
+
+       if (state <= 0)
+               return;
+       if (!conf->just_in_time_given) {
+               sched_min_delay(s);
+               return;
+       }
+       compute_chunk_time(pard->current_chunk - pard->first_chunk,
+               &afhi->chunk_tv, &pard->stream_start, &chunk_time);
+       sched_request_barrier_or_min_delay(&chunk_time, s);
+}
+
+static void afh_recv_post_select(__a_unused struct sched *s, struct task *t)
+{
+       struct receiver_node *rn = container_of(t, struct receiver_node, task);
+       struct afh_recv_args_info *conf = rn->conf;
+       struct private_afh_recv_data *pard = rn->private_data;
+       struct btr_node *btrn = rn->btrn;
+       struct afh_info *afhi = &pard->afhi;
+       int ret;
+       char *buf;
+       const char *start, *end;
+       size_t size;
+       struct timeval chunk_time;
+
+       ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
+       if (ret <= 0)
+               goto out;
+       if (pard->first_chunk > 0 && !conf->no_header_given) {
+               char *header;
+               afh_get_header(afhi, pard->audio_format_num, pard->map,
+                       pard->map_size, &header, &size);
+               if (size > 0) {
+                       PARA_INFO_LOG("writing header (%zu bytes)\n", size);
+                       buf = para_malloc(size);
+                       memcpy(buf, header, size);
+                       btr_add_output(buf, size, btrn);
+                       afh_free_header(header, pard->audio_format_num);
+               }
+       }
+       if (!conf->just_in_time_given) {
+               afh_get_chunk(pard->first_chunk, afhi, pard->map, &start, &size);
+               afh_get_chunk(pard->last_chunk, afhi, pard->map, &end, &size);
+               end += size;
+               PARA_INFO_LOG("adding %zu bytes\n", end - start);
+               btr_add_output_dont_free(start, end - start, btrn);
+               ret = -E_RECV_EOF;
+               goto out;
+       }
+       if (pard->current_chunk == pard->first_chunk)
+               pard->stream_start = *now;
+       else {
+               compute_chunk_time(pard->current_chunk - pard->first_chunk,
+                       &afhi->chunk_tv, &pard->stream_start, &chunk_time);
+               ret = tv_diff(&chunk_time, now, NULL);
+               if (ret > 0)
+                       goto out;
+       }
+       afh_get_chunk(pard->current_chunk, afhi, pard->map, &start, &size);
+       PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk);
+       btr_add_output_dont_free(start, size, btrn);
+       if (pard->current_chunk >= pard->last_chunk) {
+               ret = -E_RECV_EOF;
+               goto out;
+       }
+       pard->current_chunk++;
+       ret = 1;
+out:
+       if (ret < 0) {
+               btr_remove_node(&rn->btrn);
+               pard->current_chunk = pard->first_chunk;
+       }
+       t->error = ret;
+}
+
+/**
+ * The init function of the afh receiver.
+ *
+ * \param r Pointer to the receiver struct to initialize.
+ *
+ * This initializes all function pointers of \a r.
+ */
+void afh_recv_init(struct receiver *r)
+{
+       struct afh_recv_args_info dummy;
+
+       afh_init();
+       afh_recv_cmdline_parser_init(&dummy);
+       r->open = afh_recv_open;
+       r->close = afh_recv_close;
+       r->pre_select = afh_recv_pre_select;
+       r->post_select = afh_recv_post_select;
+       r->parse_config = afh_recv_parse_config;
+       r->free_config = afh_recv_free_config;
+       r->execute = afh_execute;
+       r->help = (struct ggo_help) {
+               .short_help = afh_recv_args_info_help,
+               .detailed_help = afh_recv_args_info_detailed_help
+       };
+       afh_recv_cmdline_parser_free(&dummy);
+}
diff --git a/afs.c b/afs.c
index f1e28f14982779ee0fff694ddf95a97c3e6bf318..1f3fafacec1c757b47659c77d2bf2da1062e3cbf 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -744,7 +744,7 @@ static void afs_signal_post_select(struct sched *s, struct task *t)
        }
        PARA_EMERG_LOG("terminating on signal %d\n", signum);
 shutdown:
-       sched_shutdown(s);
+       task_notify_all(s, E_AFS_SIGNAL);
        t->error = -E_AFS_SIGNAL;
 }
 
@@ -921,10 +921,16 @@ static void command_post_select(struct sched *s, struct task *t)
        struct afs_client *client, *tmp;
        int fd, ret;
 
+       ret = task_get_notification(t);
+       if (ret < 0) {
+               t->error = ret;
+               return;
+       }
        ret = execute_server_command(&s->rfds);
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               sched_shutdown(s);
+               task_notify_all(s, -ret);
+               t->error = ret;
                return;
        }
        /* Check the list of connected clients. */
index 16497bdf222d8b16c155fe41ab22999bc60f366e..3f4380a11b8c768f0e775451557505d6999b6818 100644 (file)
@@ -247,8 +247,10 @@ static void alsa_write_post_select(__a_unused struct sched *s,
        snd_pcm_sframes_t frames;
        int ret;
 
+       ret = task_get_notification(t);
+       if (ret < 0)
+               goto err;
 again:
-       t->error = 0;
        ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
        if (ret == 0)
                return;
index 4fc04b5120829d057617d68d7854453c2612c6cc..4dfd5d53d4c5c1a5d7d3d3b4273231dddc6a0f5a 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -16,8 +16,8 @@
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
-#include "recv.h"
 #include "buffer_tree.h"
+#include "recv.h"
 #include "filter.h"
 #include "grab_client.h"
 #include "client.cmdline.h"
@@ -38,6 +38,8 @@ INIT_AUDIOD_ERRLISTS;
 /** define the array containing all supported audio formats */
 const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY NULL};
 
+DEFINE_RECEIVER_ARRAY;
+
 /** Defines how audiod handles one supported audio format. */
 struct audio_format_info {
        /** pointer to the receiver for this audio format */
@@ -440,21 +442,7 @@ static void close_filters(struct slot_info *s)
        s->fns = NULL;
 }
 
-/*
- * Whenever a task commits suicide by returning from post_select with t->error
- * < 0, it also removes its btr node. We do exactly that to kill a running
- * task. Note that the scheduler checks t->error also _before_ each pre/post
- * select call, so the victim will never be scheduled again.
- */
-static void kill_btrn(struct btr_node **btrnp, struct task *t, int error)
-{
-       if (t->error < 0)
-               return;
-       t->error = error;
-       btr_remove_node(btrnp);
-}
-
-static void kill_all_decoders(int error)
+static void notify_receivers(int error)
 {
        int i;
 
@@ -464,8 +452,7 @@ static void kill_all_decoders(int error)
                        continue;
                if (!s->receiver_node)
                        continue;
-               kill_btrn(&s->receiver_node->btrn, &s->receiver_node->task,
-                               error);
+               task_notify(&s->receiver_node->task, error);
        }
 }
 
@@ -1144,7 +1131,7 @@ static void start_stop_decoders(void)
                try_to_close_slot(i);
        if (audiod_status != AUDIOD_ON ||
                        !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING))
-               return kill_all_decoders(-E_NOT_PLAYING);
+               return notify_receivers(E_NOT_PLAYING);
        if (!must_start_decoder())
                return;
        ret = open_receiver(stat_task->current_audio_format_num);
@@ -1198,7 +1185,7 @@ static void status_post_select(struct sched *s, struct task *t)
                if (!st->ct)
                        goto out;
                if (st->ct->task.error >= 0) {
-                       kill_btrn(&st->ct->btrn, &st->ct->task, -E_AUDIOD_OFF);
+                       task_notify(&st->ct->task, E_AUDIOD_OFF);
                        goto out;
                }
                close_stat_pipe();
@@ -1220,15 +1207,14 @@ static void status_post_select(struct sched *s, struct task *t)
                        struct timeval diff;
                        tv_diff(now, &st->last_status_read, &diff);
                        if (diff.tv_sec > 61)
-                               kill_btrn(&st->ct->btrn, &st->ct->task,
-                                       -E_STATUS_TIMEOUT);
+                               task_notify(&st->ct->task, E_AUDIOD_OFF);
                        goto out;
                }
                btr_merge(st->btrn, st->min_iqs);
                sz = btr_next_buffer(st->btrn, &buf);
                ret = for_each_stat_item(buf, sz, update_item);
                if (ret < 0) {
-                       kill_btrn(&st->ct->btrn, &st->ct->task, ret);
+                       task_notify(&st->ct->task, E_AUDIOD_OFF);
                        goto out;
                }
                if (sz != ret) {
index a4aa6d8c3ecc3ae386cbb14515f05f8257d96e0d..d9ed11f437b0e03c2d167865571a69e2c9955e33 100644 (file)
@@ -44,6 +44,7 @@ void client_disconnect(struct client_task *ct)
        if (ct->scc.fd >= 0)
                close(ct->scc.fd);
        free_argv(ct->features);
+       ct->features = NULL;
        sc_free(ct->scc.recv);
        ct->scc.recv = NULL;
        sc_free(ct->scc.send);
@@ -333,7 +334,9 @@ static void client_post_select(struct sched *s, struct task *t)
        size_t n;
        char buf[CLIENT_BUFSIZE];
 
-       t->error = 0;
+       ret = task_get_notification(t);
+       if (ret < 0)
+               goto out;
        if (ct->scc.fd < 0)
                return;
        switch (ct->status) {
index 53465e3089d862647e7476228033b3021314d4b7..b67966fa0653cae2641a37823fcf81c2410b63ce 100644 (file)
--- a/command.c
+++ b/command.c
@@ -259,6 +259,14 @@ __printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band,
        return send_sb(scc, msg, ret, band, false);
 }
 
+/**
+ * Send an error message to a client.
+ *
+ * \param cc Client info.
+ * \param err The (positive) error code.
+ *
+ * \return The return value of the underlying call to send_sb_va().
+ */
 int send_strerror(struct command_context *cc, int err)
 {
        return cc->use_sideband?
index aacecee823aecc296f7988f5f2d04741b6194b99..f3e8fb070a2aa03254e2147c1c7392c32ad2ae24 100644 (file)
@@ -102,14 +102,18 @@ all_errlist_objs="mp3_afh afh_common net string signal time daemon
        exec send_common ggo udp_recv color fec fecdec_filter
        prebuffer_filter bitstream imdct check_wav
        wma_afh wma_common wmadec_filter buffer_tree crypt_common
-       gui gui_theme sideband"
+       gui gui_theme sideband afh_recv play"
 
-executables="recv filter audioc write client afh audiod"
+executables="recv filter audioc write client afh audiod play"
 
-recv_cmdline_objs="add_cmdline(recv http_recv dccp_recv udp_recv)"
+recv_cmdline_objs="add_cmdline(recv http_recv dccp_recv udp_recv afh_recv)"
+
+recv_errlist_objs="
+       http_recv recv_common recv time string net dccp_recv fd
+       sched stdout ggo udp_recv buffer_tree afh_recv afh_common
+       wma_afh wma_common mp3_afh
+"
 
-recv_errlist_objs="http_recv recv_common recv time string net dccp_recv
-       fd sched stdout ggo udp_recv buffer_tree"
 recv_ldflags=""
 
 filter_cmdline_objs="add_cmdline(filter compress_filter amp_filter prebuffer_filter)"
@@ -151,6 +155,17 @@ client_ldflags=""
 gui_cmdline_objs="add_cmdline(gui)"
 gui_errlist_objs="exec signal string stat ringbuffer fd gui gui_theme"
 gui_objs="$gui_cmdline_objs $gui_errlist_objs"
+play_errlist_objs="play fd sched ggo buffer_tree time string net
+       afh_recv afh_common
+       wma_afh wma_common mp3_afh
+       recv_common udp_recv http_recv dccp_recv
+       filter_common fec bitstream imdct
+       wav_filter compress_filter amp_filter prebuffer_filter fecdec_filter
+               wmadec_filter
+       write_common file_write
+"
+play_cmdline_objs="add_cmdline(http_recv dccp_recv udp_recv afh_recv compress_filter amp_filter prebuffer_filter file_write play)"
+play_ldflags="-lm"
 ########################################################################### snprintf
 # ===========================================================================
 #        http://www.nongnu.org/autoconf-archive/ax_func_snprintf.html
@@ -465,7 +480,7 @@ if test "$have_curses" = "yes"; then
        extras="$extras gui"
        executables="$executables gui"
 else
-       AC_MSG_WARN([cannot build para_gui])
+       AC_MSG_WARN([no curses lib, cannot build para_gui])
 fi
 CPPFLAGS="$OLD_CPPFLAGS"
 LDFLAGS="$OLD_LDFLAGS"
@@ -511,6 +526,10 @@ if test ${have_core_audio} = yes; then
        audiod_cmdline_objs="$audiod_cmdline_objs osx_write.cmdline"
        audiod_ldflags="$audiod_ldflags $f"
 
+       play_errlist_objs="$play_errlist_objs osx_write ipc"
+       play_cmdline_objs="$play_cmdline_objs osx_write.cmdline"
+       play_ldflags="$play_ldflags $f"
+
        write_errlist_objs="$write_errlist_objs osx_write ipc"
        write_cmdline_objs="$write_cmdline_objs osx_write.cmdline"
        write_ldflags="$write_ldflags $f"
@@ -589,10 +608,14 @@ if test "$have_vorbis" = "yes" || test "$have_speex" = "yes"; then
        server_ldflags="$server_ldflags $ogg_libs"
        filter_ldflags="$filter_ldflags $ogg_libs"
        audiod_ldflags="$audiod_ldflags $ogg_libs"
+       play_ldflags="$play_ldflags $ogg_libs"
        afh_ldflags="$afh_ldflags $ogg_libs"
+       recv_ldflags="$recv_ldflags $ogg_libs"
        all_errlist_objs="$all_errlist_objs ogg_afh_common"
        afh_errlist_objs="$afh_errlist_objs ogg_afh_common"
+       recv_errlist_objs="$recv_errlist_objs ogg_afh_common"
        server_errlist_objs="$server_errlist_objs ogg_afh_common"
+       play_errlist_objs="$play_errlist_objs ogg_afh_common"
 fi
 if test "$have_vorbis" = "yes"; then
        all_errlist_objs="$all_errlist_objs oggdec_filter ogg_afh"
@@ -602,12 +625,16 @@ if test "$have_vorbis" = "yes"; then
        server_ldflags="$server_ldflags $vorbis_libs"
        filter_ldflags="$filter_ldflags $vorbis_libs"
        audiod_ldflags="$audiod_ldflags $vorbis_libs"
+       play_ldflags="$play_ldflags $vorbis_libs"
        afh_ldflags="$afh_ldflags $vorbis_libs"
+       recv_ldflags="$recv_ldflags $vorbis_libs"
 
        server_errlist_objs="$server_errlist_objs ogg_afh"
        filter_errlist_objs="$filter_errlist_objs oggdec_filter"
        audiod_errlist_objs="$audiod_errlist_objs oggdec_filter"
+       play_errlist_objs="$play_errlist_objs oggdec_filter ogg_afh"
        afh_errlist_objs="$afh_errlist_objs ogg_afh"
+       recv_errlist_objs="$recv_errlist_objs ogg_afh"
 
        audiod_audio_formats="$audiod_audio_formats ogg"
        server_audio_formats="$server_audio_formats ogg"
@@ -622,12 +649,16 @@ if test "$have_speex" = "yes"; then
        server_ldflags="$server_ldflags $speex_libs"
        filter_ldflags="$filter_ldflags $speex_libs"
        audiod_ldflags="$audiod_ldflags $speex_libs"
+       play_ldflags="$play_ldflags $speex_libs"
        afh_ldflags="$afh_ldflags $speex_libs"
+       recv_ldflags="$recv_ldflags $speex_libs"
 
        server_errlist_objs="$server_errlist_objs spx_afh spx_common"
        filter_errlist_objs="$filter_errlist_objs spxdec_filter spx_common"
        audiod_errlist_objs="$audiod_errlist_objs spxdec_filter spx_common"
+       play_errlist_objs="$play_errlist_objs spxdec_filter spx_afh spx_common"
        afh_errlist_objs="$afh_errlist_objs spx_afh spx_common"
+       recv_errlist_objs="$recv_errlist_objs spx_afh spx_common"
 
        audiod_audio_formats="$audiod_audio_formats spx"
        server_audio_formats="$server_audio_formats spx"
@@ -662,11 +693,17 @@ if test "$have_faad" = "yes"; then
        filter_errlist_objs="$filter_errlist_objs aacdec_filter aac_common"
        afh_errlist_objs="$afh_errlist_objs aac_common aac_afh"
        audiod_errlist_objs="$audiod_errlist_objs aacdec_filter aac_common"
+       play_errlist_objs="$play_errlist_objs aacdec_filter aac_afh aac_common"
        server_errlist_objs="$server_errlist_objs aac_afh aac_common"
+       recv_errlist_objs="$recv_errlist_objs aac_afh aac_common"
+
        server_ldflags="$server_ldflags $faad_libs -lfaad"
        filter_ldflags="$filter_ldflags $faad_libs -lfaad"
        audiod_ldflags="$audiod_ldflags $faad_libs -lfaad"
+       play_ldflags="$play_ldflags $faad_libs -lfaad"
        afh_ldflags="$afh_ldflags $faad_libs -lfaad"
+       recv_ldflags="$afh_ldflags $faad_libs -lfaad"
+
        audiod_audio_formats="$audiod_audio_formats aac"
        server_audio_formats="$server_audio_formats aac"
        filters="$filters aacdec"
@@ -705,11 +742,14 @@ if test "$have_mad" = "yes"; then
        AC_DEFINE(HAVE_MAD, 1, define to 1 if you want to build the mp3dec filter)
        filter_cmdline_objs="$filter_cmdline_objs add_cmdline(mp3dec_filter)"
        audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(mp3dec_filter)"
+       play_cmdline_objs="$play_cmdline_objs add_cmdline(mp3dec_filter)"
        all_errlist_objs="$all_errlist_objs mp3dec_filter"
        filter_errlist_objs="$filter_errlist_objs mp3dec_filter"
        audiod_errlist_objs="$audiod_errlist_objs mp3dec_filter"
+       play_errlist_objs="$play_errlist_objs mp3dec_filter"
        filter_ldflags="$filter_ldflags $mad_libs -lmad"
        audiod_ldflags="$audiod_ldflags $mad_libs -lmad"
+       play_ldflags="$play_ldflags $mad_libs -lmad"
        audiod_audio_formats="$audiod_audio_formats mp3"
        filters="$filters mp3dec"
        AC_SUBST(mad_cppflags)
@@ -750,6 +790,9 @@ if test ${have_libid3tag} = yes; then
        AC_DEFINE(HAVE_LIBID3TAG, 1, define to 1 you have libid3tag)
        server_ldflags="$server_ldflags $id3tag_libs -lid3tag -lz"
        afh_ldflags="$afh_ldflags $id3tag_libs -lid3tag -lz"
+       play_ldflags="$play_ldflags -lz"
+       recv_ldflags="$recv_ldflags $id3tag_libs -lid3tag"
+       play_ldflags="$play_ldflags $id3tag_libs -lid3tag"
        AC_SUBST(id3tag_cppflags)
 else
        AC_MSG_WARN([no support for id3v2 tags])
@@ -782,12 +825,16 @@ if test "$have_flac" = "yes"; then
        all_errlist_objs="$all_errlist_objs flacdec_filter flac_afh"
        filter_errlist_objs="$filter_errlist_objs flacdec_filter"
        audiod_errlist_objs="$audiod_errlist_objs flacdec_filter"
+       play_errlist_objs="$play_errlist_objs flacdec_filter flac_afh"
        afh_errlist_objs="$afh_errlist_objs flac_afh"
        server_errlist_objs="$server_errlist_objs flac_afh"
+       recv_errlist_objs="$recv_errlist_objs flac_afh"
        filter_ldflags="$filter_ldflags $flac_libs -lFLAC"
        audiod_ldflags="$audiod_ldflags $flac_libs -lFLAC"
+       play_ldflags="$play_ldflags $flac_libs -lFLAC"
        server_ldflags="$server_ldflags $flac_libs -lFLAC"
        afh_ldflags="$afh_ldflags $flac_libs -lFLAC"
+       recv_ldflags="$afh_ldflags $flac_libs -lFLAC"
        filters="$filters flacdec"
        server_audio_formats="$server_audio_formats flac"
        audiod_audio_formats="$audiod_audio_formats flac"
@@ -808,7 +855,10 @@ msg="=> will not build oss writer"
 
 AC_CHECK_HEADER(sys/soundcard.h, [
        audiod_errlist_objs="$audiod_errlist_objs oss_write"
+       play_errlist_objs="$play_errlist_objs oss_write"
        audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(oss_write)"
+       play_cmdline_objs="$play_cmdline_objs add_cmdline(oss_write)"
+
        write_errlist_objs="$write_errlist_objs oss_write"
        write_cmdline_objs="$write_cmdline_objs add_cmdline(oss_write)"
        fade_errlist_objs="$fade_errlist_objs oss_mix"
@@ -821,6 +871,7 @@ AC_CHECK_HEADER(sys/soundcard.h, [
 
        AC_CHECK_LIB(ossaudio, _oss_ioctl, [
                        audiod_ldflags="$audiod_ldflags -lossaudio"
+                       play_ldflags="$play_ldflags -lossaudio"
                        write_ldflags="$write_ldflags -lossaudio"
                        fade_ldflags="$fade_ldflags -lossaudio"
                ]
@@ -864,6 +915,10 @@ if test "$have_alsa" = "yes"; then
        audiod_errlist_objs="$audiod_errlist_objs alsa_write"
        audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(alsa_write)"
        audiod_ldflags="$audiod_ldflags -lasound"
+       play_errlist_objs="$play_errlist_objs alsa_write"
+       play_cmdline_objs="$play_cmdline_objs add_cmdline(alsa_write)"
+       play_ldflags="$play_ldflags -lasound"
+
        write_errlist_objs="$write_errlist_objs alsa_write"
        write_cmdline_objs="$write_cmdline_objs add_cmdline(alsa_write)"
        write_ldflags="$write_ldflags -lasound"
@@ -967,6 +1022,10 @@ if test "$have_ao" = "yes"; then
        audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(ao_write)"
        audiod_ldflags="$audiod_ldflags -lao -lpthread"
 
+       play_errlist_objs="$play_errlist_objs ao_write"
+       play_cmdline_objs="$play_cmdline_objs add_cmdline(ao_write)"
+       play_ldflags="$play_ldflags -lao -lpthread"
+
        write_errlist_objs="$write_errlist_objs ao_write"
        write_cmdline_objs="$write_cmdline_objs add_cmdline(ao_write)"
        write_ldflags="$write_ldflags $ao_libs -lao -lpthread"
@@ -1002,14 +1061,21 @@ AC_CHECK_HEADERS([readline/readline.h], [
        have_readline="no"
        AC_MSG_WARN([readline/readline.h not found, $msg])
 ])
+
+if test "$have_curses" != "yes"; then
+       have_readline="no"
+       AC_MSG_WARN([interactive cli support depends on curses,])
+       AC_MSG_WARN([but no curses lib was detected, $msg])
+fi
+
 if test "$have_readline" = "yes"; then
        readline_libs="$readline_libs -lreadline"
-       AC_SEARCH_LIBS([rl_replace_line], [readline], [], [have_readline="no"])
+       AC_SEARCH_LIBS([rl_free_keymap], [readline], [], [have_readline="no"])
        if test "$have_readline" = "no"; then # try with -lcurses
                 # clear cache
                AC_MSG_NOTICE([trying again with -lcurses])
-                unset ac_cv_search_rl_replace_line 2> /dev/null
-               AC_SEARCH_LIBS([rl_replace_line], [readline], [
+                unset ac_cv_search_rl_free_keymap 2> /dev/null
+               AC_SEARCH_LIBS([rl_free_keymap], [readline], [
                        have_readline=yes
                        readline_libs="$readline_libs -lcurses"
                ], [], [-lcurses])
@@ -1017,8 +1083,8 @@ if test "$have_readline" = "yes"; then
        if test "$have_readline" = "no"; then # try with -ltermcap
                 # clear cache
                AC_MSG_NOTICE([trying again with -ltermcap])
-                unset ac_cv_search_rl_replace_line 2> /dev/null
-               AC_SEARCH_LIBS([rl_replace_line], [readline], [
+                unset ac_cv_search_rl_free_keymap 2> /dev/null
+               AC_SEARCH_LIBS([rl_free_keymap], [readline], [
                        have_readline=yes
                        readline_libs="$readline_libs -ltermcap"
                ], [], [-ltermcap])
@@ -1031,6 +1097,8 @@ if test "$have_readline" = "yes"; then
        client_ldflags="$client_ldflags $readline_libs"
        audioc_errlist_objs="$audioc_errlist_objs buffer_tree interactive sched time"
        audioc_ldflags="$audioc_ldflags $readline_libs"
+       play_errlist_objs="$play_errlist_objs interactive"
+       play_ldflags="$play_ldflags $readline_libs"
        AC_SUBST(readline_cppflags)
        AC_DEFINE(HAVE_READLINE, 1, define to 1 to turn on readline support)
 else
@@ -1067,8 +1135,11 @@ if test "$have_samplerate" = "yes"; then
        filter_cmdline_objs="$filter_cmdline_objs add_cmdline(resample_filter)"
        audiod_errlist_objs="$audiod_errlist_objs resample_filter check_wav"
        audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(resample_filter)"
+       play_errlist_objs="$play_errlist_objs resample_filter check_wav"
+       play_cmdline_objs="$play_cmdline_objs add_cmdline(resample_filter)"
        filter_ldflags="$filter_ldflags $samplerate_libs -lsamplerate"
        audiod_ldflags="$audiod_ldflags $samplerate_libs -lsamplerate"
+       play_ldflags="$play_ldflags $samplerate_libs -lsamplerate"
        filters="$filters resample"
        AC_SUBST(samplerate_cppflags)
 else
@@ -1128,6 +1199,8 @@ write_objs="$write_cmdline_objs $write_errlist_objs"
 client_objs="$client_cmdline_objs $client_errlist_objs"
 audioc_objs="$audioc_cmdline_objs $audioc_errlist_objs"
 afh_objs="$afh_cmdline_objs $afh_errlist_objs"
+play_objs="$play_cmdline_objs $play_errlist_objs"
+
 
 AC_SUBST(recv_objs, add_dot_o($recv_objs))
 AC_SUBST(recv_ldflags, $recv_ldflags)
@@ -1173,6 +1246,13 @@ AC_SUBST(gui_objs, add_dot_o($gui_objs))
 AC_DEFINE_UNQUOTED(INIT_GUI_ERRLISTS,
        objlist_to_errlist($gui_errlist_objs), errors used by para_gui)
 
+AC_SUBST(play_objs, add_dot_o($play_objs))
+AC_SUBST(play_ldflags, $play_ldflags)
+AC_DEFINE_UNQUOTED(INIT_PLAY_ERRLISTS,
+       objlist_to_errlist($play_errlist_objs), errors used by para_play)
+
+AC_MSG_NOTICE(play objs: $play_objs)
+
 enum="$(for i in $filters; do printf "${i}_FILTER, " | tr '[a-z]' '[A-Z]'; done)"
 AC_DEFINE_UNQUOTED(FILTER_ENUM, $enum NUM_SUPPORTED_FILTERS,
        enum of supported filters)
diff --git a/crypt.c b/crypt.c
index b754c0919e6fbccca77c0d662bddf59c6351fca6..17026cc63525c5527f63fb95c27242dc272add23 100644 (file)
--- a/crypt.c
+++ b/crypt.c
@@ -319,19 +319,26 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
 
 void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
 {
+       size_t len = src->iov_len, l1, l2;
        RC4_KEY *key = &sc->key;
 
+       assert(len > 0);
+       assert(len < ((typeof(src->iov_len))-1) / 2);
+       l1 = ROUND_DOWN(len, RC4_ALIGN);
+       l2 = ROUND_UP(len, RC4_ALIGN);
+
        *dst = (typeof(*dst)) {
-               /*
-                * Add one for the terminating zero byte. Integer overflow is
-                * no problem here as para_malloc() aborts when given a zero
-                * size argument.
-                */
-               .iov_base = para_malloc(src->iov_len + 1),
-               .iov_len = src->iov_len
+               /* Add one for the terminating zero byte. */
+               .iov_base = para_malloc(l2 + 1),
+               .iov_len = len
        };
-       RC4(key, src->iov_len, src->iov_base, dst->iov_base);
-       ((char *)dst->iov_base)[dst->iov_len] = '\0';
+       RC4(key, l1, src->iov_base, dst->iov_base);
+       if (len > l1) {
+               unsigned char remainder[RC4_ALIGN] = "";
+               memcpy(remainder, src->iov_base + l1, len - l1);
+               RC4(key, len - l1, remainder, dst->iov_base + l1);
+       }
+       ((char *)dst->iov_base)[len] = '\0';
 }
 
 void hash_function(const char *data, unsigned long len, unsigned char *hash)
index ea6884b16a1ffcda3002eb6b6340b2cff257b7be..69bc186126c2447e5f5d68306a070bd0953a2b1d 100644 (file)
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "recv.h"
 #include "string.h"
 #include "net.h"
 #include "fd.h"
-#include "buffer_tree.h"
 
 #include "dccp_recv.cmdline.h"
 
@@ -132,6 +132,9 @@ static void dccp_recv_post_select(struct sched *s, struct task *t)
        int ret, iovcnt;
        size_t num_bytes;
 
+       ret = task_get_notification(t);
+       if (ret < 0)
+               goto out;
        ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
        if (ret <= 0)
                goto out;
diff --git a/error.h b/error.h
index 422ddfd263fba2d08e05dcfe0cb6eb73b1aac23b..164f8adbf11d4bcf57251fc5832af23ce630d76f 100644 (file)
--- a/error.h
+++ b/error.h
@@ -57,6 +57,13 @@ extern const char **para_errlist[];
        PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
 
 
+#define PLAY_ERRORS \
+       PARA_ERROR(PLAY_SYNTAX, "para_play: syntax error"), \
+       PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \
+       PARA_ERROR(TERM_RQ, "user termination request"), \
+       PARA_ERROR(BAD_PLAY_CMD, "invalid command"), \
+
+
 #define FLACDEC_FILTER_ERRORS \
        PARA_ERROR(FLACDEC_DECODER_ALLOC, "could not allocate stream decoder"), \
        PARA_ERROR(FLACDEC_DECODER_INIT, "could not init stream decoder"), \
@@ -75,6 +82,10 @@ extern const char **para_errlist[];
        PARA_ERROR(FLAC_STREAMINFO, "could not read stream info meta block"), \
 
 
+#define AFH_RECV_ERRORS \
+       PARA_ERROR(AFH_RECV_BAD_FILENAME, "invalid file name"), \
+
+
 #define OGG_AFH_COMMON_ERRORS \
        PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \
        PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
@@ -488,6 +499,7 @@ extern const char **para_errlist[];
 #define INTERACTIVE_ERRORS \
        PARA_ERROR(I9E_EOF, "end of input"), \
        PARA_ERROR(I9E_SETUPTERM, "failed to set up terminal"), \
+       PARA_ERROR(I9E_TERM_RQ, "received termination request"), \
 
 /** \endcond errors */
 
index 32f6c3abdffdee58f8c4b49087c01b66c3909c1b..13008c2dd4dcce7850b7fdd508dea827790d1787 100644 (file)
@@ -110,7 +110,9 @@ static void file_write_post_select(__a_unused struct sched *s,
        char *buf;
        size_t bytes;
 
-       t->error = 0;
+       ret = task_get_notification(t);
+       if (ret < 0)
+               goto out;
        ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
        if (ret <= 0)
                goto out;
index 11602f9544e34c36eb54763852d1d5cf63d7f2cc..48673afb478aadd242d5ad4829d78623beedf1f9 100644 (file)
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "recv.h"
 #include "http_recv.cmdline.h"
 #include "net.h"
 #include "string.h"
 #include "fd.h"
-#include "buffer_tree.h"
 
 /**
  * the possible states of a http receiver node
@@ -83,7 +83,9 @@ static void http_recv_post_select(struct sched *s, struct task *t)
        struct iovec iov[2];
        size_t num_bytes;
 
-       t->error = 0;
+       ret = task_get_notification(t);
+       if (ret < 0)
+               goto out;
        ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
        if (ret < 0)
                goto out;
index 68891ac10a4f0a48e491a0eaf2c82033a36335ae..44db6c58fda6b4080c0874725c7a9bd4b9f6fbe4 100644 (file)
 struct i9e_private {
        struct i9e_client_info *ici;
        FILE *stderr_stream;
+       int num_columns;
        char empty_line[1000];
        struct task task;
        struct btr_node *stdout_btrn;
+       bool last_write_was_status;
        bool line_handler_running;
        bool input_eof;
        bool caught_sigint;
+       bool caught_sigterm;
+       Keymap standard_km;
+       Keymap bare_km;
 };
 static struct i9e_private i9e_private, *i9ep = &i9e_private;
 
+/**
+ * Return the error state of the i9e task.
+ *
+ * This is mainly useful for other tasks to tell whether the i9e task is still
+ * running.
+ *
+ * \return A negative return value of zero means the i9e task terminated. Only
+ * in this case it is safe to call ie9_close().
+ */
+int i9e_get_error(void)
+{
+       return i9ep->task.error;
+}
+
 static bool is_prefix(const char *partial, const char *full, size_t len)
 {
        if (len == 0)
@@ -178,11 +197,47 @@ static char **i9e_completer(const char *text, int start, __a_unused int end)
  */
 void i9e_attach_to_stdout(struct btr_node *producer)
 {
-       assert(!i9ep->stdout_btrn);
+       btr_remove_node(&i9ep->stdout_btrn);
        i9ep->stdout_btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "interactive_stdout", .parent = producer));
+       rl_set_keymap(i9ep->bare_km);
+}
+
+static void wipe_bottom_line(void)
+{
+       char x[] = "          ";
+       int n = i9ep->num_columns;
+
+       /*
+        * For reasons beyond my understanding, writing more than 68 characters
+        * here causes MacOS to mess up the terminal. Writing a line of spaces
+        * in smaller chunks works fine though. Weird.
+        */
+       fprintf(i9ep->stderr_stream, "\r");
+       while (n > 0) {
+               if (n >= sizeof(x)) {
+                       fprintf(i9ep->stderr_stream, "%s", x);
+                       n -= sizeof(x);
+                       continue;
+               }
+               x[n] = '\0';
+               fprintf(i9ep->stderr_stream, "%s", x);
+               break;
+       }
+       fprintf(i9ep->stderr_stream, "\r");
 }
 
+/**
+ * Free all storage associated with a keymap.
+ *
+ * This function is not declared in the readline headers although the symbol is
+ * exported and the function is documented in the readline info file. So we
+ * have to declare it here.
+ *
+ * \param keymap The keymap to deallocate.
+ */
+void rl_free_keymap(Keymap keymap);
+
 /**
  * Reset the terminal and save the in-memory command line history.
  *
@@ -192,16 +247,11 @@ void i9e_close(void)
 {
        char *hf = i9ep->ici->history_file;
 
-       rl_deprep_terminal();
-       fprintf(i9ep->stderr_stream, "\n");
+       rl_free_keymap(i9ep->bare_km);
+       rl_callback_handler_remove();
        if (hf)
                write_history(hf);
-       fclose(i9ep->stderr_stream);
-}
-
-static void wipe_bottom_line(void)
-{
-       fprintf(i9ep->stderr_stream, "\r%s\r", i9ep->empty_line);
+       wipe_bottom_line();
 }
 
 static void clear_bottom_line(void)
@@ -241,9 +291,7 @@ static void i9e_line_handler(char *line)
 {
        int ret;
 
-       i9ep->line_handler_running = true;
        ret = i9ep->ici->line_handler(line);
-       i9ep->line_handler_running = false;
        if (ret < 0)
                PARA_WARNING_LOG("%s\n", para_strerror(-ret));
        rl_set_prompt("");
@@ -259,57 +307,68 @@ static void i9e_line_handler(char *line)
        }
 }
 
-static void i9e_input(void)
-{
-       do {
-               rl_callback_read_char();
-       } while (input_available());
-}
-
-static void i9e_post_select(struct sched *s, struct task *t)
+static void i9e_post_select(__a_unused struct sched *s, struct task *t)
 {
        int ret;
-       struct btr_node *btrn = i9ep->stdout_btrn;
        struct i9e_client_info *ici = i9ep->ici;
        char *buf;
-       size_t sz;
+       size_t sz, consumed = 0;
 
-       if (i9ep->input_eof) {
-               t->error = -E_I9E_EOF;
-               return;
-       }
-       if (!btrn) {
-               i9ep->caught_sigint = false;
-               if (FD_ISSET(ici->fds[0], &s->rfds))
-                       i9e_input();
-               return;
-       }
+       ret = -E_I9E_EOF;
+       if (i9ep->input_eof)
+               goto rm_btrn;
+       ret = -E_I9E_TERM_RQ;
+       if (i9ep->caught_sigterm)
+               goto rm_btrn;
+       ret = 0;
        if (i9ep->caught_sigint)
                goto rm_btrn;
+       while (input_available())
+               rl_callback_read_char();
+       if (!i9ep->stdout_btrn)
+               goto out;
        ret = btr_node_status(i9ep->stdout_btrn, 0, BTR_NT_LEAF);
-       if (ret < 0)
+       if (ret < 0) {
+               ret = 0;
                goto rm_btrn;
-       sz = btr_next_buffer(btrn, &buf);
+       }
+       if (ret == 0)
+               goto out;
+again:
+       sz = btr_next_buffer(i9ep->stdout_btrn, &buf);
        if (sz == 0)
                goto out;
+       if (i9ep->last_write_was_status)
+               fprintf(i9ep->stderr_stream, "\n");
+       i9ep->last_write_was_status = false;
        ret = xwrite(ici->fds[1], buf, sz);
        if (ret < 0)
                goto rm_btrn;
-       btr_consume(btrn, ret);
+       btr_consume(i9ep->stdout_btrn, ret);
+       consumed += ret;
+       if (ret == sz && consumed < 10000)
+               goto again;
        goto out;
 rm_btrn:
-       btr_remove_node(&i9ep->stdout_btrn);
-       rl_set_prompt(i9ep->ici->prompt);
-       rl_forced_update_display();
+       if (i9ep->stdout_btrn) {
+               wipe_bottom_line();
+               btr_remove_node(&i9ep->stdout_btrn);
+               rl_set_keymap(i9ep->standard_km);
+               rl_set_prompt(i9ep->ici->prompt);
+               rl_redisplay();
+       }
+       if (ret < 0)
+               wipe_bottom_line();
 out:
-       t->error = 0;
+       i9ep->caught_sigint = false;
+       t->error = ret;
 }
 
 static void i9e_pre_select(struct sched *s, __a_unused struct task *t)
 {
        int ret;
 
-       if (i9ep->input_eof || i9ep->caught_sigint) {
+       if (i9ep->input_eof || i9ep->caught_sigint || i9ep->caught_sigterm) {
                sched_min_delay(s);
                return;
        }
@@ -338,14 +397,30 @@ static void update_winsize(void)
 {
        struct winsize w;
        int ret = ioctl(i9ep->ici->fds[2], TIOCGWINSZ, (char *)&w);
-       int num_columns = 80;
 
        if (ret >= 0) {
                assert(w.ws_col < sizeof(i9ep->empty_line));
-               num_columns = w.ws_col;
-       }
-       memset(i9ep->empty_line, ' ', num_columns);
-       i9ep->empty_line[num_columns] = '\0';
+               i9ep->num_columns = w.ws_col;
+       } else
+               i9ep->num_columns = 80;
+
+       memset(i9ep->empty_line, ' ', i9ep->num_columns);
+       i9ep->empty_line[i9ep->num_columns] = '\0';
+}
+
+/**
+ * Defined key sequences are mapped to keys starting with this offset. I.e.
+ * pressing the first defined key sequence yields the key number \p KEY_OFFSET.
+ */
+#define KEY_OFFSET 64
+
+static int dispatch_key(__a_unused int count, int key)
+{
+       int ret;
+
+       assert(key >= KEY_OFFSET);
+       ret = i9ep->ici->key_handler(key - KEY_OFFSET);
+       return ret < 0? ret : 0;
 }
 
 /**
@@ -382,16 +457,34 @@ int i9e_open(struct i9e_client_info *ici, struct sched *s)
        i9ep->stderr_stream = fdopen(ici->fds[2], "w");
        setvbuf(i9ep->stderr_stream, NULL, _IONBF, 0);
 
+       i9ep->standard_km = rl_get_keymap();
+       i9ep->bare_km = rl_make_bare_keymap();
+       if (ici->bound_keyseqs) {
+               char *seq;
+               int i;
+               /* FIXME: This is an arbitrary constant.  */
+               for (i = 0; i < 32 && (seq = ici->bound_keyseqs[i]); i++) {
+                       char buf[2] = {KEY_OFFSET + i, '\0'};
+                       /* readline needs an allocated buffer for the macro */
+                       rl_generic_bind(ISMACR, seq, para_strdup(buf), i9ep->bare_km);
+                       rl_bind_key_in_map(KEY_OFFSET + i, dispatch_key, i9ep->bare_km);
+               }
+       }
        if (ici->history_file)
                read_history(ici->history_file);
        update_winsize();
-       rl_callback_handler_install(i9ep->ici->prompt, i9e_line_handler);
+       if (ici->producer) {
+               rl_callback_handler_install("", i9e_line_handler);
+               i9e_attach_to_stdout(ici->producer);
+               rl_set_keymap(i9ep->bare_km);
+       } else
+               rl_callback_handler_install(i9ep->ici->prompt, i9e_line_handler);
        return 1;
 }
 
 static void reset_line_state(void)
 {
-       if (i9ep->line_handler_running)
+       if (i9ep->stdout_btrn)
                return;
        rl_on_new_line();
        rl_reset_line_state();
@@ -414,12 +507,46 @@ __printf_2_3 void i9e_log(int ll, const char* fmt,...)
 
        if (ll < i9ep->ici->loglevel)
                return;
-       if (i9ep->line_handler_running == false)
-               clear_bottom_line();
+       clear_bottom_line();
        va_start(argp, fmt);
        vfprintf(i9ep->stderr_stream, fmt, argp);
        va_end(argp);
        reset_line_state();
+       i9ep->last_write_was_status = false;
+}
+
+/**
+ * Print the current status to stderr.
+ *
+ * \param buf The text to print.
+ * \param len The number of bytes in \a buf.
+ *
+ * This clears the bottom line, moves to the beginning of the line and prints
+ * the given text. If the length of this text exceeds the width of the
+ * terminal, the text is shortened by leaving out a part in the middle.
+ */
+void ie9_print_status_bar(char *buf, unsigned len)
+{
+       size_t x = i9ep->num_columns, y = (x - 4) / 2;
+
+       assert(x >= 6);
+       if (len > x) {
+               buf[y] = '\0';
+               fprintf(i9ep->stderr_stream, "\r%s", buf);
+               fprintf(i9ep->stderr_stream, " .. ");
+               fprintf(i9ep->stderr_stream, "%s", buf + len - y);
+       } else {
+               char scratch[1000];
+
+               y = x - len;
+               scratch[0] = '\r';
+               strcpy(scratch + 1, buf);
+               memset(scratch + 1 + len, ' ', y);
+               scratch[1 + len + y] = '\r';
+               scratch[2 + len + y] = '\0';
+               fprintf(i9ep->stderr_stream, "\r%s", scratch);
+       }
+       i9ep->last_write_was_status = true;
 }
 
 /**
@@ -431,12 +558,16 @@ __printf_2_3 void i9e_log(int ll, const char* fmt,...)
  */
 void i9e_signal_dispatch(int sig_num)
 {
+       if (sig_num == SIGWINCH)
+               return update_winsize();
        if (sig_num == SIGINT) {
                fprintf(i9ep->stderr_stream, "\n");
                rl_replace_line ("", false /* clear_undo */);
                reset_line_state();
                i9ep->caught_sigint = true;
        }
+       if (sig_num == SIGTERM)
+               i9ep->caught_sigterm = true;
 }
 
 /**
index a19f8a4736700c99f60c84f6448ba5e9092885ac..8e436755bef81d726a1ed1a71dcd2f4a0fea8231 100644 (file)
@@ -59,6 +59,10 @@ struct i9e_client_info {
        int loglevel;
        /** Complete input lines are passed to this callback function. */
        int (*line_handler)(char *line);
+       /** In single key mode, this callback is executed instead. */
+       int (*key_handler)(int key);
+       /** The array of valid key sequences for libreadline. */
+       char **bound_keyseqs;
        /** File descriptors to use for input/output/log. */
        int fds[3];
        /** Text of the current prompt. */
@@ -71,10 +75,16 @@ struct i9e_client_info {
         * completer if the cursor is not on the first word.
         */
        struct i9e_completer *completers;
+       /**
+        * If non-NULL, this node is attached immediately to the stdout btr
+        * node of the i9e subsystem.
+        */
+       struct btr_node *producer;
 };
 
 int i9e_open(struct i9e_client_info *ici, struct sched *s);
 void i9e_attach_to_stdout(struct btr_node *producer);
+void ie9_print_status_bar(char *buf, unsigned len);
 void i9e_close(void);
 void i9e_signal_dispatch(int sig_num);
 __printf_2_3 void i9e_log(int ll, const char* fmt,...);
@@ -86,3 +96,4 @@ char **i9e_complete_commands(const char *word, struct i9e_completer *completers)
 void i9e_complete_option(char **opts, struct i9e_completion_info *ci,
                struct i9e_completion_result *cr);
 int i9e_print_completions(struct i9e_completer *completers);
+int i9e_get_error(void);
index 8adb29cf3036c9e881538773766aaac884768108..beb846961b79dde7b7bdbdf9663c671b5857e080 100644 (file)
@@ -3,136 +3,41 @@ args "--unamed-opts=audio_file --no-handle-version"
 include(header.m4)
 <qu>
 text "
-para_afh, the audio format handler tool, is a stand-alone program
-contained in the paraslash package for analyzing and streaming audio
-files. It can be used to
-
-       - print tech info about the given audio file to stdout.
-       In particular, the 'chunk table' of the audio file, an array
-       of offsets within the audio file, may be printed. This table
-       can be used by other programs unaware of the particular audio
-       format to stream the audio file.
-
-       - write selected parts of the given audio file in complete
-       chunks without decoding. Thus para_afh can be used to 'cut'
-       an audio file.
-
-       - write selected parts of the given audio files 'just in time'
-       to stdout. This may be useful for third-party software that
-       is capable of reading from stdin.
+para_afh, the audio format handler tool, is a simple program for analyzing
+audio files. It prints technical information about the given audio file to
+stdout.
 "
 </qu>
 
 include(loglevel.m4)
 
 <qu>
-defgroup "mode"
-#--------------
-groupdesc="
-       There are two modes of operation: Info mode and stream mode,
-       one of which must be selected by the corresponding option.
-       See below.
-"
-required
-
-groupoption "info" i
-#~~~~~~~~~~~~~~~~~~~
-"select info mode"
-group="mode"
-details="
-       In this mode, the program prints technical information about
-       the given audio file to stdout.
-"
-
-groupoption "stream" s
-#~~~~~~~~~~~~~~~~~~~~~
-"select stream mode"
-group="mode"
-details="
-       If this mode is selected, the selected parts of the content
-       of the audio file are written to stdout. Only complete chunks
-       with respect of the underlying audio format are written.
-       For example, only complete frames in case of mp3 files.
-"
-
-section "Options for info mode"
-#==============================
-
 option "chunk_table" c
 #~~~~~~~~~~~~~~~~~~~~~
 "print also the chunk table"
 flag off
-dependon="info"
-
-option "human" u
-#~~~~~~~~~~~~~~~
-"use human-readable output format"
-flag off
-dependon = "info"
 details = "
-       Currently this option only affects the format of the chunk table,
-       so it has no effect if --chunk_table is not given.
+       The 'chunk table' of an audio file is an array of offsets
+       within the audio file. Each offset corresponds to chunk
+       of encoded data. The exact meaning of 'chunk' depends on
+       the audio format.
 
-       The human-readable output consists of one output line per
-       chunk and the output contains also the chunk number, the
-       duration and the size of each chunk.
+       Programs which are unaware of the particular audio format can
+       read the chunk table to obtain the timing information needed
+       to stream the file.
 "
 
-section "Options for stream mode"
-#================================
-
-
-option "begin_chunk" b
-#~~~~~~~~~~~~~~~~~~~~~
-"skip a number of chunks"
-int typestr="chunk_num"
-default="0"
-dependon="stream"
-optional
-details="
-       The chunk_num argument must be between -num_chunks and
-       num_chunks - 1 inclusively where num_chunks is the total number
-       of chunks which is printed when using the --info option. If
-       chunk_num is negative, the given number of chunks are counted
-       backwards from the end of the file. For example --begin_chunk
-       -100 instructs para_afh to start output at chunk num_chunks
-       - 100. This is mainly useful for cutting off the end of an
-       audio file.
-"
-
-option "end_chunk" e
-#~~~~~~~~~~~~~~~~~~~
-"only write up to chunk chunk_num"
-int typestr="chunk_num"
-dependon="stream"
-optional
-details="
-       For the chunk_num argument the same rules as for --begin_chunk
-       apply.  The default is to write up to the last chunk.
-"
-
-option "just_in_time" j
-#~~~~~~~~~~~~~~~~~~~~~~
-"use timed writes"
+option "parser-friendly" p
+#~~~~~~~~~~~~~~~~~~~~~~~~~
+"do not use human-readable output format"
 flag off
-dependon="stream"
-details="
-       Write the specified chunks of data 'just in time', i.e. the
-       write of each chunk is delayed until the time it is needed
-       by the decoder/player in order to guarantee an uninterrupted
-       audio stream.
-"
+details = "
+       Currently this option only affects the format of the chunk table,
+       so it has no effect if --chunk_table is not given.
 
-option "no_header" H
-#~~~~~~~~~~~~~~~~~~~
-"do not write an audio file header"
-flag off
-dependon="stream"
-details="
-       If an audio format needs information about the audio file
-       in a format-specific header in order to be understood by
-       the decoding software, a suitable header is automatically
-       send. This option changes the default behaviour, i.e. no
-       header is written.
+       The human-readable output (the default) consists of one output
+       line per chunk and the output contains also the chunk number,
+       the duration and the size of each chunk. The parser-friendly
+       output prints only the offsets, in one line.
 "
 </qu>
diff --git a/m4/gengetopt/afh_recv.m4 b/m4/gengetopt/afh_recv.m4
new file mode 100644 (file)
index 0000000..4995e77
--- /dev/null
@@ -0,0 +1,71 @@
+include(header.m4)
+<qu>
+text "
+       The afh (audio format handler) receiver can be used to write
+       selected parts of the given audio file without decoding
+       the data.
+
+       The selected parts of the content of the audio file are passed
+       to the child nodes of the buffer tree. Only complete chunks
+       with respect of the underlying audio format are passed.
+
+"
+
+option "filename" f
+#~~~~~~~~~~~~~~~~~~
+"file to open"
+string typestr = "filename"
+required
+
+option "begin-chunk" b
+#~~~~~~~~~~~~~~~~~~~~~
+"skip a number of chunks"
+int typestr = "chunk_num"
+default = "0"
+optional
+details = "
+       The chunk_num argument must be between -num_chunks and
+       num_chunks - 1 inclusively where num_chunks is the total number
+       of chunks which is printed when using the --info option. If
+       chunk_num is negative, the given number of chunks are counted
+       backwards from the end of the file. For example --begin_chunk
+       -100 instructs para_afh to start output at chunk num_chunks
+       - 100. This is mainly useful for cutting off the end of an
+       audio file.
+"
+
+option "end-chunk" e
+#~~~~~~~~~~~~~~~~~~~
+"only write up to chunk chunk_num"
+int typestr = "chunk_num"
+optional
+details = "
+       For the chunk_num argument the same rules as for --begin_chunk
+       apply. The default is to write up to the last chunk.
+"
+
+option "just-in-time" j
+#~~~~~~~~~~~~~~~~~~~~~~
+"use timed writes"
+flag off
+details = "
+       Write the specified chunks of data 'just in time', i.e. the
+       write of each chunk is delayed until the time it is needed
+       by the decoder/player in order to guarantee an uninterrupted
+       audio stream. This may be useful for third-party software
+       that is capable of reading from stdin.
+"
+
+option "no-header" H
+#~~~~~~~~~~~~~~~~~~~
+"do not write an audio file header"
+flag off
+details = "
+       If an audio format needs information about the audio file
+       in a format-specific header in order to be understood by
+       the decoding software, a suitable header is automatically
+       send. This option changes the default behaviour, i.e. no
+       header is written.
+"
+
+</qu>
index c3b42f441ffa4b445c193849180b51404c0d0009..138ac30b909f4b95bb0d2d50921f9930e4493840 100644 (file)
@@ -41,6 +41,8 @@ $(ggo_dir)/resample_filter.ggo: \
        $(m4_ggo_dir)/channels.m4 \
        $(m4_ggo_dir)/sample_rate.m4 \
        $(m4_ggo_dir)/sample_format.m4
+$(ggo_dir)/play.ggo: $(m4_ggo_dir)/loglevel.m4 $(m4_ggo_dir)/config_file.m4
+
 $(ggo_dir)/%.ggo: $(m4_ggo_dir)/%.m4 $(m4_ggo_dir)/header.m4 | $(ggo_dir)
        @[ -z "$(Q)" ] || echo 'M4 $<'
        $(Q) m4 -I $(m4_ggo_dir) $< > $@
diff --git a/m4/gengetopt/play.m4 b/m4/gengetopt/play.m4
new file mode 100644 (file)
index 0000000..57f954c
--- /dev/null
@@ -0,0 +1,38 @@
+args "--unamed-opts=audio_file --no-handle-version --conf-parser --no-handle-help"
+include(header.m4)
+define(CURRENT_PROGRAM,para_play)
+define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf)
+define(DEFAULT_HISTORY_FILE,~/.paraslash/play.history)
+
+<qu>
+#########################
+section "General options"
+#########################
+</qu>
+include(loglevel.m4)
+include(config_file.m4)
+include(history_file.m4)
+<qu>
+
+###############################
+section "Options for para_play"
+###############################
+
+option "randomize" z
+#~~~~~~~~~~~~~~~~~~~
+"randomize playlist at startup."
+flag off
+
+option "key_map" k
+#~~~~~~~~~~~~~~~~~
+"Map key k to a command."
+
+string typestr = "key:command [args]"
+optional
+multiple
+details = "
+       This option may be given multiple times, one for each key
+       mapping. Example:
+               5:jmp 50
+"
+</qu>
index d2dc963eaeb03f3b5cc520248109cef0cac6b08b..8ca3c525a664b3faf307d5c5d66c6886e7a3c8eb 100644 (file)
@@ -164,13 +164,15 @@ static void oss_post_select(__a_unused struct sched *s,
        struct private_oss_write_data *powd = wn->private_data;
        struct btr_node *btrn = wn->btrn;
        size_t frames, bytes;
-       int ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+       int ret;
        char *data;
 
+       ret = task_get_notification(t);
        if (ret < 0)
                goto out;
-       if (ret == 0)
-               return;
+       ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+       if (ret <= 0)
+               goto out;
        if (!powd) {
                int32_t rate, ch, format;
                get_btr_sample_rate(btrn, &rate);
index 2894f11eb6672dd5f06fa1f3166f6a77afad7ae0..b057b9c057991864b582ad8519cbac1b9083a90d 100644 (file)
@@ -308,15 +308,18 @@ static void osx_write_post_select(__a_unused struct sched *s, struct task *t)
        struct btr_node *btrn = wn->btrn;
        int ret;
 
+       ret = task_get_notification(t);
+       if (ret < 0)
+               goto fail;
        if (!powd) {
                ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
                if (ret == 0)
                        return;
                if (ret < 0)
-                       goto remove_btrn;
+                       goto fail;
                ret = core_audio_init(wn);
                if (ret < 0)
-                       goto remove_btrn;
+                       goto fail;
                powd = wn->private_data;
                ret = -E_UNIT_START;
                if (AudioOutputUnitStart(powd->audio_unit) != noErr) {
@@ -332,16 +335,17 @@ static void osx_write_post_select(__a_unused struct sched *s, struct task *t)
                btr_pushdown(btrn);
        if (ret < 0 && need_drain_delay(powd))
                ret = 0;
-       if (ret >= 0) {
-               mutex_unlock(powd->mutex);
+       mutex_unlock(powd->mutex);
+       if (ret >= 0)
                return;
+fail:
+       assert(ret < 0);
+       if (powd && powd->callback_btrn) {
+               AudioOutputUnitStop(powd->audio_unit);
+               AudioUnitUninitialize(powd->audio_unit);
+               CloseComponent(powd->audio_unit);
+               btr_remove_node(&powd->callback_btrn);
        }
-       AudioOutputUnitStop(powd->audio_unit);
-       AudioUnitUninitialize(powd->audio_unit);
-       CloseComponent(powd->audio_unit);
-       btr_remove_node(&powd->callback_btrn);
-       mutex_unlock(powd->mutex);
-remove_btrn:
        btr_remove_node(&wn->btrn);
        PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
        t->error = ret;
diff --git a/play.c b/play.c
new file mode 100644 (file)
index 0000000..047c959
--- /dev/null
+++ b/play.c
@@ -0,0 +1,1281 @@
+/*
+ * Copyright (C) 2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file play.c Paraslash's standalone player. */
+
+#include <regex.h>
+#include <sys/time.h>
+#include <fnmatch.h>
+#include <signal.h>
+
+#include "para.h"
+#include "list.h"
+#include "play.cmdline.h"
+#include "filter.cmdline.h"
+#include "error.h"
+#include "ggo.h"
+#include "buffer_tree.h"
+#include "version.h"
+#include "string.h"
+#include "sched.h"
+#include "filter.h"
+#include "afh.h"
+#include "recv.h"
+#include "write.h"
+#include "write_common.h"
+#include "fd.h"
+
+/**
+ * Besides playback tasks which correspond to the receiver/filter/writer nodes,
+ * para_play creates two further tasks: The play task and the i9e task. It is
+ * important whether a function can be called in the context of para_play or
+ * i9e or both. As a rule, all command handlers are called only in i9e context via
+ * the line handler (input mode) or the key handler (command mode) below.
+ *
+ * Playlist handling is done exclusively in play context.
+ */
+
+/**
+ * Describes a request to change the state of para_play.
+ *
+ * There is only one variable of this type: \a rq of the global play task
+ * structure. Command handlers only set this variable and the post_select()
+ * function of the play task investigates its value during each iteration of
+ * the scheduler run and performs the actual work.
+ */
+enum state_change_request_type {
+       /** Everybody is happy. */
+       CRT_NONE,
+       /** Stream must be repositioned (com_jmp(), com_ff()). */
+       CRT_REPOS,
+       /** New file should be loaded (com_next()). */
+       CRT_FILE_CHANGE,
+       /** Someone wants us for dead (com_quit()). */
+       CRT_TERM_RQ
+};
+
+struct play_task {
+       struct task task;
+       /* A bit array of invalid files (those will be skipped). */
+       bool *invalid;
+       /* The file which is currently open. */
+       unsigned current_file;
+       /* When to update the status again. */
+       struct timeval next_update;
+
+       /* Root of the buffer tree for command and status output. */
+       struct btr_node *btrn;
+
+       /* The decoding machinery.  */
+       struct receiver_node rn;
+       struct filter_node fn;
+       struct writer_node wn;
+
+       /* See comment to enum state_change_request_type above */
+       enum state_change_request_type rq;
+       /* only relevant if rq == CRT_FILE_CHANGE */
+       unsigned next_file;
+       /*
+               bg: read lines at prompt, fg: display status and wait
+               for keystroke.
+       */
+       bool background;
+
+       /* We have the *intention* to play. Set by com_play(). */
+       bool playing;
+
+       /* as returned by afh_recv->open() */
+       int audio_format_num;
+
+       /* retrieved via the btr exec mechanism */
+       long unsigned start_chunk;
+       long unsigned seconds;
+       long unsigned num_chunks;
+       char *afhi_txt;
+};
+
+/** Initialize the array of errors for para_play. */
+INIT_PLAY_ERRLISTS;
+
+/* Activate the afh receiver. */
+extern void afh_recv_init(struct receiver *r);
+#undef AFH_RECEIVER
+/** Initialization code for a receiver struct. */
+#define AFH_RECEIVER {.name = "afh", .init = afh_recv_init},
+/** This expands to the array of all receivers. */
+DEFINE_RECEIVER_ARRAY;
+
+static int loglevel = LL_WARNING;
+
+/** The log function which writes log messages to stderr. */
+INIT_STDERR_LOGGING(loglevel);
+
+char *stat_item_values[NUM_STAT_ITEMS] = {NULL};
+
+/** Iterate over all files in the playlist. */
+#define FOR_EACH_PLAYLIST_FILE(i) for (i = 0; i < conf.inputs_num; i++)
+static struct play_args_info conf;
+
+static struct sched sched = {.max_fileno = 0};
+static struct play_task play_task;
+static struct receiver *afh_recv;
+
+static void check_afh_receiver_or_die(void)
+{
+       int i;
+
+       FOR_EACH_RECEIVER(i) {
+               struct receiver *r = receivers + i;
+               if (strcmp(r->name, "afh"))
+                       continue;
+               afh_recv = r;
+               return;
+       }
+       PARA_EMERG_LOG("fatal: afh receiver not found\n");
+       exit(EXIT_FAILURE);
+}
+
+/** Description to be included in the --detailed-help output. */
+#define PP_DESC \
+"para_play is a command line audio player.\n" \
+"\n" \
+"It operates either in command mode or in insert mode. In insert mode it\n" \
+"presents a prompt and allows to enter para_play commands like stop, play, pause\n" \
+"etc. In command mode, the current audio file is shown and the program reads\n" \
+"single key strokes from stdin. Keys may be mapped to para_play commands.\n" \
+"Whenever a mapped key is pressed, the associated command is executed.\n" \
+
+__noreturn static void print_help_and_die(void)
+{
+       int d = conf.detailed_help_given;
+       const char **p = d? play_args_info_detailed_help
+               : play_args_info_help;
+
+//     printf_or_die("%s\n\n", PLAY_CMDLINE_PARSER_PACKAGE "-"
+//             PLAY_CMDLINE_PARSER_VERSION);
+
+       printf_or_die("%s\n\n", play_args_info_usage);
+       if (d)
+               printf_or_die("%s\n", PP_DESC);
+       for (; *p; p++)
+               printf_or_die("%s\n", *p);
+       exit(0);
+}
+
+static void parse_config_or_die(int argc, char *argv[])
+{
+       int i, ret;
+       char *config_file;
+       struct play_cmdline_parser_params params = {
+               .override = 0,
+               .initialize = 1,
+               .check_required = 0,
+               .check_ambiguity = 0,
+               .print_errors = 1
+       };
+
+       if (play_cmdline_parser_ext(argc, argv, &conf, &params))
+               exit(EXIT_FAILURE);
+       HANDLE_VERSION_FLAG("play", conf);
+       if (conf.help_given || conf.detailed_help_given)
+               print_help_and_die();
+       loglevel = get_loglevel_by_name(conf.loglevel_arg);
+       if (conf.config_file_given)
+               config_file = para_strdup(conf.config_file_arg);
+       else {
+               char *home = para_homedir();
+               config_file = make_message("%s/.paraslash/play.conf", home);
+               free(home);
+       }
+       ret = file_exists(config_file);
+       if (conf.config_file_given && !ret) {
+               PARA_EMERG_LOG("can not read config file %s\n", config_file);
+               goto err;
+       }
+       if (ret) {
+               params.initialize = 0;
+               params.check_required = 1;
+               play_cmdline_parser_config_file(config_file, &conf, &params);
+       }
+       for (i = 0; i < conf.key_map_given; i++) {
+               char *s = strchr(conf.key_map_arg[i] + 1, ':');
+               if (s)
+                       continue;
+               PARA_EMERG_LOG("invalid key map arg: %s\n", conf.key_map_arg[i]);
+               goto err;
+       }
+       free(config_file);
+       return;
+err:
+       free(config_file);
+       exit(EXIT_FAILURE);
+}
+
+static char get_playback_state(struct play_task *pt)
+{
+       switch (pt->rq) {
+       case CRT_NONE: return pt->playing? 'P' : 'U';
+       case CRT_REPOS: return 'R';
+       case CRT_FILE_CHANGE: return 'F';
+       case CRT_TERM_RQ: return 'X';
+       }
+       assert(false);
+};
+
+static long unsigned get_play_time(struct play_task *pt)
+{
+       char state = get_playback_state(pt);
+       long unsigned result;
+
+       if (state != 'P' && state != 'U')
+               return 0;
+       if (pt->num_chunks == 0 || pt->seconds == 0)
+               return 0;
+       /* where the stream started (in seconds) */
+       result = pt->start_chunk * pt->seconds / pt->num_chunks;
+       if (pt->wn.btrn) { /* Add the uptime of the writer node */
+               struct timeval diff = {.tv_sec = 0}, wstime;
+               btr_get_node_start(pt->wn.btrn, &wstime);
+               if (wstime.tv_sec > 0)
+                       tv_diff(now, &wstime, &diff);
+               result += diff.tv_sec;
+       }
+       result = PARA_MIN(result, pt->seconds);
+       result = PARA_MAX(result, 0UL);
+       return result;
+}
+
+static void wipe_receiver_node(struct play_task *pt)
+{
+       PARA_NOTICE_LOG("cleaning up receiver node\n");
+       btr_remove_node(&pt->rn.btrn);
+       afh_recv->close(&pt->rn);
+       afh_recv->free_config(pt->rn.conf);
+       memset(&pt->rn, 0, sizeof(struct receiver_node));
+}
+
+/* returns: 0 not eof, 1: eof, < 0: fatal error.  */
+static int get_playback_error(struct play_task *pt)
+{
+       int err = pt->wn.task.error;
+
+       if (err >= 0)
+               return 0;
+       if (pt->fn.task.error >= 0)
+               return 0;
+       if (pt->rn.task.error >= 0)
+               return 0;
+       if (err == -E_BTR_EOF || err == -E_RECV_EOF || err == -E_EOF
+                       || err == -E_WRITE_COMMON_EOF)
+               return 1;
+       return err;
+}
+
+static int eof_cleanup(struct play_task *pt)
+{
+       struct writer *w = writers + DEFAULT_WRITER;
+       struct filter *decoder = filters + pt->fn.filter_num;
+       int ret;
+
+       ret = get_playback_error(pt);
+       if (ret == 0)
+               return ret;
+       PARA_NOTICE_LOG("cleaning up wn/fn nodes\n");
+       w->close(&pt->wn);
+       btr_remove_node(&pt->wn.btrn);
+       w->free_config(pt->wn.conf);
+       memset(&pt->wn, 0, sizeof(struct writer_node));
+
+       decoder->close(&pt->fn);
+       btr_remove_node(&pt->fn.btrn);
+       free(pt->fn.conf);
+       memset(&pt->fn, 0, sizeof(struct filter_node));
+
+       btr_remove_node(&pt->rn.btrn);
+       /*
+        * On eof (ret > 0), we do not wipe the receiver node struct until a
+        * new file is loaded because we still need it for jumping around when
+        * paused.
+        */
+       if (ret < 0)
+               wipe_receiver_node(pt);
+       return ret;
+}
+
+static int shuffle_compare(__a_unused const void *a, __a_unused const void *b)
+{
+       return para_random(100) - 50;
+}
+
+static void shuffle(char **base, size_t num)
+{
+       srandom(now->tv_sec);
+       qsort(base, num, sizeof(char *), shuffle_compare);
+}
+
+static struct btr_node *new_recv_btrn(struct receiver_node *rn)
+{
+       return btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = afh_recv->name, .context = rn,
+                       .handler = afh_recv->execute));
+}
+
+static int open_new_file(struct play_task *pt)
+{
+       int ret;
+       char *tmp, *path = conf.inputs[pt->next_file], *afh_recv_conf[] =
+               {"play", "-f", path, "-b", "0", NULL};
+
+       PARA_NOTICE_LOG("next file: %s\n", path);
+       wipe_receiver_node(pt);
+       pt->start_chunk = 0;
+       pt->rn.btrn = new_recv_btrn(&pt->rn);
+       pt->rn.conf = afh_recv->parse_config(ARRAY_SIZE(afh_recv_conf) - 1,
+               afh_recv_conf);
+       assert(pt->rn.conf);
+       pt->rn.receiver = afh_recv;
+       ret = afh_recv->open(&pt->rn);
+       if (ret < 0) {
+               PARA_ERROR_LOG("could not open %s: %s\n", path,
+                       para_strerror(-ret));
+               goto fail;
+       }
+       pt->audio_format_num = ret;
+       free(pt->afhi_txt);
+       ret = btr_exec_up(pt->rn.btrn, "afhi", &pt->afhi_txt);
+       if (ret < 0)
+               pt->afhi_txt = make_message("[afhi command failed]\n");
+       ret = btr_exec_up(pt->rn.btrn, "seconds_total", &tmp);
+       if (ret < 0)
+               pt->seconds = 1;
+       else {
+               int32_t x;
+               ret = para_atoi32(tmp, &x);
+               pt->seconds = ret < 0? 1 : x;
+               free(tmp);
+               tmp = NULL;
+       }
+       ret = btr_exec_up(pt->rn.btrn, "chunks_total", &tmp);
+       if (ret < 0)
+               pt->num_chunks = 1;
+       else {
+               int32_t x;
+               ret = para_atoi32(tmp, &x);
+               pt->num_chunks = ret < 0? 1 : x;
+               free(tmp);
+               tmp = NULL;
+       }
+       pt->rn.task.pre_select = afh_recv->pre_select;
+       pt->rn.task.post_select = afh_recv->post_select;
+       sprintf(pt->rn.task.status, "%s receiver node", afh_recv->name);
+       return 1;
+fail:
+       wipe_receiver_node(pt);
+       return ret;
+}
+
+static int load_file(struct play_task *pt)
+{
+       const char *af;
+       char *tmp;
+       int ret;
+       struct filter *decoder;
+
+       btr_remove_node(&pt->rn.btrn);
+       if (!pt->rn.receiver || pt->next_file != pt->current_file) {
+               ret = open_new_file(pt);
+               if (ret < 0)
+                       return ret;
+       } else {
+               char buf[20];
+               pt->rn.btrn = new_recv_btrn(&pt->rn);
+               sprintf(buf, "repos %lu", pt->start_chunk);
+               ret = btr_exec_up(pt->rn.btrn, buf, &tmp);
+               if (ret < 0)
+                       PARA_CRIT_LOG("repos failed: %s\n", para_strerror(-ret));
+               freep(&tmp);
+       }
+       if (!pt->playing)
+               return 0;
+       /* set up decoding filter */
+       af = audio_format_name(pt->audio_format_num);
+       tmp = make_message("%sdec", af);
+       ret = check_filter_arg(tmp, &pt->fn.conf);
+       freep(&tmp);
+       if (ret < 0)
+               goto fail;
+       pt->fn.filter_num = ret;
+       decoder = filters + ret;
+       pt->fn.task.pre_select = decoder->pre_select;
+       pt->fn.task.post_select = decoder->post_select;
+       sprintf(pt->fn.task.status, "%s decoder", af);
+       pt->fn.btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = decoder->name, .parent = pt->rn.btrn,
+                       .handler = decoder->execute, .context = &pt->fn));
+       decoder->open(&pt->fn);
+
+       /* setup default writer */
+       pt->wn.conf = check_writer_arg_or_die(NULL, &pt->wn.writer_num);
+       pt->wn.task.error = 0;
+
+       /* success, register tasks */
+       register_task(&sched, &pt->rn.task);
+       register_task(&sched, &pt->fn.task);
+       register_writer_node(&pt->wn, pt->fn.btrn, &sched);
+       return 1;
+fail:
+       wipe_receiver_node(pt);
+       return ret;
+}
+
+static int next_valid_file(struct play_task *pt)
+{
+       int i, j = pt->current_file;
+
+       FOR_EACH_PLAYLIST_FILE(i) {
+               j = (j + 1) % conf.inputs_num;
+               if (!pt->invalid[j])
+                       return j;
+       }
+       return -E_NO_VALID_FILES;
+}
+
+static int load_next_file(struct play_task *pt)
+{
+       int ret;
+
+again:
+       if (pt->rq == CRT_NONE || pt->rq == CRT_FILE_CHANGE) {
+               pt->start_chunk = 0;
+               ret = next_valid_file(pt);
+               if (ret < 0)
+                       return ret;
+               pt->next_file = ret;
+       } else if (pt->rq == CRT_REPOS)
+               pt->next_file = pt->current_file;
+       ret = load_file(pt);
+       if (ret < 0) {
+               pt->invalid[pt->next_file] = true;
+               pt->rq = CRT_NONE;
+               goto again;
+       }
+       pt->current_file = pt->next_file;
+       pt->rq = CRT_NONE;
+       return ret;
+}
+
+static void kill_stream(struct play_task *pt)
+{
+       task_notify(&pt->wn.task, E_EOF);
+}
+
+#ifdef HAVE_READLINE
+
+/* only called from com_prev(), nec. only if we have readline */
+static int previous_valid_file(struct play_task *pt)
+{
+       int i, j = pt->current_file;
+
+       FOR_EACH_PLAYLIST_FILE(i) {
+               j--;
+               if (j < 0)
+                       j = conf.inputs_num - 1;
+               if (!pt->invalid[j])
+                       return j;
+       }
+       return -E_NO_VALID_FILES;
+}
+
+#include "interactive.h"
+
+/*
+ * Define the default (internal) key mappings and helper functions to get the
+ * key sequence or the command from a key id, which is what we obtain from
+ * i9e/readline when the key is pressed.
+ *
+ * In some of these helper functions we could return pointers to the constant
+ * arrays defined below. However, for others we can not, so let's better be
+ * consistent and allocate all returned strings on the heap.
+ */
+
+#define INTERNAL_KEYMAP_ENTRIES \
+       KEYMAP_ENTRY("^", "jmp 0"), \
+       KEYMAP_ENTRY("1", "jmp 10"), \
+       KEYMAP_ENTRY("2", "jmp 21"), \
+       KEYMAP_ENTRY("3", "jmp 32"), \
+       KEYMAP_ENTRY("4", "jmp 43"), \
+       KEYMAP_ENTRY("5", "jmp 54"), \
+       KEYMAP_ENTRY("6", "jmp 65"), \
+       KEYMAP_ENTRY("7", "jmp 76"), \
+       KEYMAP_ENTRY("8", "jmp 87"), \
+       KEYMAP_ENTRY("9", "jmp 98"), \
+       KEYMAP_ENTRY("+", "next"), \
+       KEYMAP_ENTRY("-", "prev"), \
+       KEYMAP_ENTRY(":", "bg"), \
+       KEYMAP_ENTRY("i", "info"), \
+       KEYMAP_ENTRY("l", "ls"), \
+       KEYMAP_ENTRY("s", "play"), \
+       KEYMAP_ENTRY("p", "pause"), \
+       KEYMAP_ENTRY("q", "quit"), \
+       KEYMAP_ENTRY("?", "help"), \
+       KEYMAP_ENTRY("\033[D", "ff -10"), \
+       KEYMAP_ENTRY("\033[C", "ff 10"), \
+       KEYMAP_ENTRY("\033[A", "ff 60"), \
+       KEYMAP_ENTRY("\033[B", "ff -60"), \
+
+#define KEYMAP_ENTRY(a, b) a
+static const char *default_keyseqs[] = {INTERNAL_KEYMAP_ENTRIES};
+#undef KEYMAP_ENTRY
+#define KEYMAP_ENTRY(a, b) b
+static const char *default_commands[] = {INTERNAL_KEYMAP_ENTRIES};
+#undef KEYMAP_ENTRY
+#define NUM_INTERNALLY_MAPPED_KEYS ARRAY_SIZE(default_commands)
+#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + conf.key_map_given)
+#define FOR_EACH_MAPPED_KEY(i) for (i = 0; i < NUM_MAPPED_KEYS; i++)
+
+static inline bool is_internal_key(int key)
+{
+       return key < NUM_INTERNALLY_MAPPED_KEYS;
+}
+
+/* for internal keys, the key id is just the array index. */
+static inline int get_internal_key_map_idx(int key)
+{
+       assert(is_internal_key(key));
+       return key;
+}
+
+/*
+ * For user-defined keys, we have to subtract NUM_INTERNALLY_MAPPED_KEYS. The
+ * difference is the index to the array of user defined key maps.
+ */
+static inline int get_user_key_map_idx(int key)
+{
+       assert(!is_internal_key(key));
+       return key - NUM_INTERNALLY_MAPPED_KEYS;
+}
+
+static inline int get_key_map_idx(int key)
+{
+       return is_internal_key(key)?
+               get_internal_key_map_idx(key) : get_user_key_map_idx(key);
+}
+
+static inline char *get_user_key_map_arg(int key)
+{
+       return conf.key_map_arg[get_user_key_map_idx(key)];
+}
+
+static inline char *get_internal_key_map_seq(int key)
+{
+       return para_strdup(default_keyseqs[get_internal_key_map_idx(key)]);
+}
+
+static char *get_user_key_map_seq(int key)
+{
+       const char *kma = get_user_key_map_arg(key);
+       const char *p = strchr(kma + 1, ':');
+       char *result;
+       int len;
+
+       if (!p)
+               return NULL;
+       len = p - kma;
+       result = para_malloc(len + 1);
+       memcpy(result, kma, len);
+       result[len] = '\0';
+       return result;
+}
+
+static char *get_key_map_seq(int key)
+{
+       return is_internal_key(key)?
+               get_internal_key_map_seq(key) : get_user_key_map_seq(key);
+}
+
+static inline char *get_internal_key_map_cmd(int key)
+{
+       return para_strdup(default_commands[get_internal_key_map_idx(key)]);
+}
+
+static char *get_user_key_map_cmd(int key)
+{
+       const char *kma = get_user_key_map_arg(key);
+       const char *p = strchr(kma + 1, ':');
+
+       if (!p)
+               return NULL;
+       return para_strdup(p + 1);
+}
+
+static char *get_key_map_cmd(int key)
+{
+       return is_internal_key(key)?
+               get_internal_key_map_cmd(key) : get_user_key_map_cmd(key);
+}
+
+static char **get_mapped_keyseqs(void)
+{
+       char **result;
+       int i;
+
+       result = para_malloc((NUM_MAPPED_KEYS + 1) * sizeof(char *));
+       FOR_EACH_MAPPED_KEY(i) {
+               int idx = get_key_map_idx(i);
+               char *seq = get_key_map_seq(i);
+               char *cmd = get_key_map_cmd(i);
+               bool internal = is_internal_key(i);
+               PARA_DEBUG_LOG("%s key sequence #%d: %s -> %s\n",
+                       internal? "internal" : "user-defined",
+                       idx, seq, cmd);
+               result[i] = seq;
+               free(cmd);
+       }
+       result[i] = NULL;
+       return result;
+}
+
+#include "play_completion.h"
+
+
+/* defines one command of para_play */
+struct pp_command {
+       const char *name;
+       int (*handler)(struct play_task *, int, char**);
+       const char *description;
+       const char *usage;
+       const char *help;
+};
+
+#include "play_command_list.h"
+static struct pp_command pp_cmds[] = {DEFINE_PLAY_CMD_ARRAY};
+#define FOR_EACH_COMMAND(c) for (c = 0; pp_cmds[c].name; c++)
+
+#include "play_completion.h"
+static struct i9e_completer pp_completers[];
+
+I9E_DUMMY_COMPLETER(jmp);
+I9E_DUMMY_COMPLETER(next);
+I9E_DUMMY_COMPLETER(prev);
+I9E_DUMMY_COMPLETER(fg);
+I9E_DUMMY_COMPLETER(bg);
+I9E_DUMMY_COMPLETER(ls);
+I9E_DUMMY_COMPLETER(info);
+I9E_DUMMY_COMPLETER(play);
+I9E_DUMMY_COMPLETER(pause);
+I9E_DUMMY_COMPLETER(stop);
+I9E_DUMMY_COMPLETER(tasks);
+I9E_DUMMY_COMPLETER(quit);
+I9E_DUMMY_COMPLETER(ff);
+
+static void help_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *result)
+{
+       result->matches = i9e_complete_commands(ci->word, pp_completers);
+}
+
+static struct i9e_completer pp_completers[] = {PLAY_COMPLETERS {.name = NULL}};
+
+static void attach_stdout(struct play_task *pt, const char *name)
+{
+       if (pt->btrn)
+               return;
+       pt->btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = name));
+       i9e_attach_to_stdout(pt->btrn);
+}
+
+static void detach_stdout(struct play_task *pt)
+{
+       btr_remove_node(&pt->btrn);
+}
+
+static int com_quit(struct play_task *pt, int argc, __a_unused char **argv)
+{
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+       pt->rq = CRT_TERM_RQ;
+       return 0;
+}
+
+static int com_help(struct play_task *pt, int argc, char **argv)
+{
+       int i;
+       char *buf;
+       size_t sz;
+
+       if (argc > 2)
+               return -E_PLAY_SYNTAX;
+       if (argc < 2) {
+               if (pt->background)
+                       FOR_EACH_COMMAND(i) {
+                               sz = xasprintf(&buf, "%s\t%s\n", pp_cmds[i].name,
+                                       pp_cmds[i].description);
+                               btr_add_output(buf, sz, pt->btrn);
+                       }
+               else {
+                       FOR_EACH_MAPPED_KEY(i) {
+                               bool internal = is_internal_key(i);
+                               int idx = get_key_map_idx(i);
+                               char *seq = get_key_map_seq(i);
+                               char *cmd = get_key_map_cmd(i);
+                               sz = xasprintf(&buf,
+                                       "%s key #%d: %s -> %s\n",
+                                       internal? "internal" : "user-defined",
+                                       idx, seq, cmd);
+                               btr_add_output(buf, sz, pt->btrn);
+                               free(seq);
+                               free(cmd);
+                       }
+               }
+               return 0;
+       }
+       FOR_EACH_COMMAND(i) {
+               if (strcmp(pp_cmds[i].name, argv[1]))
+                       continue;
+               sz = xasprintf(&buf,
+                       "NAME\n\t%s -- %s\n"
+                       "SYNOPSIS\n\t%s\n"
+                       "DESCRIPTION\n%s\n",
+                       argv[1],
+                       pp_cmds[i].description,
+                       pp_cmds[i].usage,
+                       pp_cmds[i].help
+               );
+               btr_add_output(buf, sz, pt->btrn);
+               return 0;
+       }
+       return -E_BAD_PLAY_CMD;
+}
+
+static int com_info(struct play_task *pt, int argc, __a_unused char **argv)
+{
+       char *buf;
+       size_t sz;
+       static char dflt[] = "[no information available]";
+
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+       sz = xasprintf(&buf, "playlist_pos: %u\npath: %s\n",
+               pt->current_file, conf.inputs[pt->current_file]);
+       btr_add_output(buf, sz, pt->btrn);
+       buf = pt->afhi_txt? pt->afhi_txt : dflt;
+       btr_add_output_dont_free(buf, strlen(buf), pt->btrn);
+       return 0;
+}
+
+static void list_file(struct play_task *pt, int num)
+{
+       char *buf;
+       size_t sz;
+
+       sz = xasprintf(&buf, "%s %4u %s\n", num == pt->current_file?
+               "*" : " ", num, conf.inputs[num]);
+       btr_add_output(buf, sz, pt->btrn);
+}
+
+static int com_tasks(struct play_task *pt, int argc, __a_unused char **argv)
+{
+       static char state;
+       char *buf;
+       size_t sz;
+
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+
+       buf = get_task_list(&sched);
+       btr_add_output(buf, strlen(buf), pt->btrn);
+       state = get_playback_state(pt);
+       sz = xasprintf(&buf, "state: %c\n", state);
+       btr_add_output(buf, sz, pt->btrn);
+       return 0;
+}
+
+static int com_ls(struct play_task *pt, int argc, char **argv)
+{
+       int i, j, ret;
+
+       if (argc == 1) {
+               FOR_EACH_PLAYLIST_FILE(i)
+                       list_file(pt, i);
+               return 0;
+       }
+       for (j = 1; j < argc; j++) {
+               FOR_EACH_PLAYLIST_FILE(i) {
+                       ret = fnmatch(argv[j], conf.inputs[i], 0);
+                       if (ret == 0) /* match */
+                               list_file(pt, i);
+               }
+       }
+       return 0;
+}
+
+static int com_play(struct play_task *pt, int argc, char **argv)
+{
+       int32_t x;
+       int ret;
+       char state;
+
+       if (argc > 2)
+               return -E_PLAY_SYNTAX;
+       state = get_playback_state(pt);
+       if (argc == 1) {
+               if (state == 'P')
+                       return 0;
+               pt->next_file = pt->current_file;
+               pt->rq = CRT_REPOS;
+               pt->playing = true;
+               return 0;
+       }
+       ret = para_atoi32(argv[1], &x);
+       if (ret < 0)
+               return ret;
+       if (x < 0 || x >= conf.inputs_num)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       kill_stream(pt);
+       pt->next_file = x;
+       pt->rq = CRT_FILE_CHANGE;
+       return 0;
+}
+
+static int com_pause(struct play_task *pt, int argc, __a_unused char **argv)
+{
+       char state;
+       long unsigned seconds, ss;
+
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+       state = get_playback_state(pt);
+       pt->playing = false;
+       if (state != 'P')
+               return 0;
+       seconds = get_play_time(pt);
+       pt->playing = false;
+       ss = 0;
+       if (pt->seconds > 0)
+               ss = seconds * pt->num_chunks / pt->seconds + 1;
+       ss = PARA_MAX(ss, 0UL);
+       ss = PARA_MIN(ss, pt->num_chunks);
+       pt->start_chunk = ss;
+       kill_stream(pt);
+       return 0;
+}
+
+static int com_prev(struct play_task *pt, int argc, __a_unused char **argv)
+
+{
+       int ret;
+
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+       ret = previous_valid_file(pt);
+       if (ret < 0)
+               return ret;
+       kill_stream(pt);
+       pt->next_file = ret;
+       pt->rq = CRT_FILE_CHANGE;
+       return 0;
+}
+
+static int com_next(struct play_task *pt, int argc, __a_unused char **argv)
+{
+       int ret;
+
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+       ret = next_valid_file(pt);
+       if (ret < 0)
+               return ret;
+       kill_stream(pt);
+       pt->next_file = ret;
+       pt->rq = CRT_FILE_CHANGE;
+       return 0;
+}
+
+static int com_fg(struct play_task *pt, int argc, __a_unused char **argv)
+{
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+       pt->background = false;
+       return 0;
+}
+
+static int com_bg(struct play_task *pt, int argc, __a_unused char **argv)
+{
+       if (argc != 1)
+               return -E_PLAY_SYNTAX;
+       pt->background = true;
+       return 0;
+}
+
+static int com_jmp(struct play_task *pt, int argc, char **argv)
+{
+       int32_t percent;
+       int ret;
+
+       if (argc != 2)
+               return -E_PLAY_SYNTAX;
+       ret = para_atoi32(argv[1], &percent);
+       if (ret < 0)
+               return ret;
+       if (percent < 0 || percent > 100)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       if (pt->playing && !pt->fn.btrn)
+               return 0;
+       pt->start_chunk = percent * pt->num_chunks / 100;
+       if (!pt->playing)
+               return 0;
+       pt->rq = CRT_REPOS;
+       kill_stream(pt);
+       return 0;
+}
+
+static int com_ff(struct play_task *pt, int argc, char **argv)
+{
+       int32_t seconds;
+       int ret;
+
+       if (argc != 2)
+               return -E_PLAY_SYNTAX;
+       ret = para_atoi32(argv[1], &seconds);
+       if (ret < 0)
+               return ret;
+       if (pt->playing && !pt->fn.btrn)
+               return 0;
+       seconds += get_play_time(pt);
+       seconds = PARA_MIN(seconds, (typeof(seconds))pt->seconds - 4);
+       seconds = PARA_MAX(seconds, 0);
+       pt->start_chunk = pt->num_chunks * seconds / pt->seconds;
+       pt->start_chunk = PARA_MIN(pt->start_chunk, pt->num_chunks - 1);
+       pt->start_chunk = PARA_MAX(pt->start_chunk, 0UL);
+       if (!pt->playing)
+               return 0;
+       pt->rq = CRT_REPOS;
+       kill_stream(pt);
+       return 0;
+}
+
+static int run_command(char *line, struct play_task *pt)
+{
+       int i, ret, argc;
+       char **argv = NULL;
+
+       attach_stdout(pt, __FUNCTION__);
+       ret = create_argv(line, " ", &argv);
+       if (ret < 0) {
+               PARA_ERROR_LOG("parse error: %s\n", para_strerror(-ret));
+               return 0;
+       }
+       if (ret == 0)
+               goto out;
+       argc = ret;
+       FOR_EACH_COMMAND(i) {
+               if (strcmp(pp_cmds[i].name, argv[0]))
+                       continue;
+               ret = pp_cmds[i].handler(pt, argc, argv);
+               if (ret < 0)
+                       PARA_WARNING_LOG("%s: %s\n", pt->background?
+                               "" : argv[0], para_strerror(-ret));
+               ret = 1;
+               goto out;
+       }
+       PARA_WARNING_LOG("invalid command: %s\n", argv[0]);
+       ret = 0;
+out:
+       free_argv(argv);
+       return ret;
+}
+
+static int play_i9e_line_handler(char *line)
+{
+       struct play_task *pt = &play_task;
+       int ret;
+
+       if (line == NULL || !*line)
+               return 0;
+       ret = run_command(line, pt);
+       if (ret < 0)
+               return ret;
+       return 0;
+}
+
+static int play_i9e_key_handler(int key)
+{
+       struct play_task *pt = &play_task;
+       int idx = get_key_map_idx(key);
+       char *seq = get_key_map_seq(key);
+       char *cmd = get_key_map_cmd(key);
+       bool internal = is_internal_key(key);
+
+       PARA_NOTICE_LOG("pressed %d: %s key #%d (%s -> %s)\n",
+               key, internal? "internal" : "user-defined",
+               idx, seq, cmd);
+       run_command(cmd, pt);
+       free(seq);
+       free(cmd);
+       pt->next_update = *now;
+       return 0;
+}
+
+static struct i9e_client_info ici = {
+       .fds = {0, 1, 2},
+       .prompt = "para_play> ",
+       .line_handler = play_i9e_line_handler,
+       .key_handler = play_i9e_key_handler,
+       .completers = pp_completers,
+};
+
+static void sigint_handler(int sig)
+{
+       play_task.background = true;
+       i9e_signal_dispatch(sig);
+}
+
+/*
+ * We start with para_log() set to the standard log function which writes to
+ * stderr. Once the i9e subsystem has been initialized, we switch to the i9e
+ * log facility.
+ */
+static void session_open(__a_unused struct play_task *pt)
+{
+       int ret;
+       char *history_file;
+       struct sigaction act;
+
+       PARA_NOTICE_LOG("\n%s\n", VERSION_TEXT("play"));
+       if (conf.history_file_given)
+               history_file = para_strdup(conf.history_file_arg);
+       else {
+               char *home = para_homedir();
+               history_file = make_message("%s/.paraslash/play.history",
+                       home);
+               free(home);
+       }
+       ici.history_file = history_file;
+       ici.loglevel = loglevel;
+
+       act.sa_handler = sigint_handler;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = 0;
+       sigaction(SIGINT, &act, NULL);
+       act.sa_handler = i9e_signal_dispatch;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = 0;
+       sigaction(SIGWINCH, &act, NULL);
+       sched.select_function = i9e_select;
+
+       ici.bound_keyseqs = get_mapped_keyseqs();
+       pt->btrn = ici.producer = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = __FUNCTION__));
+       ret = i9e_open(&ici, &sched);
+       if (ret < 0)
+               goto out;
+       para_log = i9e_log;
+       return;
+out:
+       free(history_file);
+       if (ret >= 0)
+               return;
+       PARA_EMERG_LOG("fatal: %s\n", para_strerror(-ret));
+       exit(EXIT_FAILURE);
+}
+
+static void session_update_time_string(struct play_task *pt, char *str, unsigned len)
+{
+       if (pt->background)
+               return;
+       if (pt->btrn) {
+               if (btr_get_output_queue_size(pt->btrn) > 0)
+                       return;
+               if (btr_get_input_queue_size(pt->btrn) > 0)
+                       return;
+       }
+       ie9_print_status_bar(str, len);
+}
+
+/*
+ * If we are about to die we must call i9e_close() to reset the terminal.
+ * However, i9e_close() must be called in *this* context, i.e. from
+ * play_task.post_select() rather than from i9e_post_select(), because
+ * otherwise i9e would access freed memory upon return. So the play task must
+ * stay alive until the i9e task terminates.
+ *
+ * We achieve this by sending a fake SIGTERM signal via i9e_signal_dispatch()
+ * and reschedule. In the next iteration, i9e->post_select returns an error and
+ * terminates. Subsequent calls to i9e_get_error() then return negative and we
+ * are allowed to call i9e_close() and terminate as well.
+ */
+static int session_post_select(__a_unused struct sched *s, struct task *t)
+{
+       struct play_task *pt = container_of(t, struct play_task, task);
+       int ret;
+
+       if (pt->background)
+               detach_stdout(pt);
+       else
+               attach_stdout(pt, __FUNCTION__);
+       ret = i9e_get_error();
+       if (ret < 0) {
+               kill_stream(pt);
+               i9e_close();
+               para_log = stderr_log;
+               free(ici.history_file);
+               return ret;
+       }
+       if (get_playback_state(pt) == 'X')
+               i9e_signal_dispatch(SIGTERM);
+       return 0;
+}
+
+#else /* HAVE_READLINE */
+
+static int session_post_select(struct sched *s, struct task *t)
+{
+       struct play_task *pt = container_of(t, struct play_task, task);
+       char c;
+
+       if (!FD_ISSET(STDIN_FILENO, &s->rfds))
+               return 0;
+       if (read(STDIN_FILENO, &c, 1))
+               do_nothing;
+       kill_stream(pt);
+       return 1;
+}
+
+static void session_open(__a_unused struct play_task *pt)
+{
+}
+
+static void session_update_time_string(__a_unused struct play_task *pt,
+               char *str, __a_unused unsigned len)
+{
+       printf("\r%s     ", str);
+       fflush(stdout);
+}
+#endif /* HAVE_READLINE */
+
+static void play_pre_select(struct sched *s, struct task *t)
+{
+       struct play_task *pt = container_of(t, struct play_task, task);
+       char state;
+
+       para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno);
+       state = get_playback_state(pt);
+       if (state == 'R' || state == 'F' || state == 'X')
+               return sched_min_delay(s);
+       sched_request_barrier_or_min_delay(&pt->next_update, s);
+}
+
+static unsigned get_time_string(struct play_task *pt, char **result)
+{
+       int seconds, length;
+       char state = get_playback_state(pt);
+
+       /* do not return anything if things are about to change */
+       if (state != 'P' && state != 'U') {
+               *result = NULL;
+               return 0;
+       }
+       length = pt->seconds;
+       if (length == 0)
+               return xasprintf(result, "0:00 [0:00] (0%%/0:00)");
+       seconds = get_play_time(pt);
+       return xasprintf(result, "#%u: %d:%02d [%d:%02d] (%d%%/%d:%02d) %s",
+               pt->current_file,
+               seconds / 60,
+               seconds % 60,
+               (length - seconds) / 60,
+               (length - seconds) % 60,
+               length? (seconds * 100 + length / 2) / length : 0,
+               length / 60,
+               length % 60,
+               conf.inputs[pt->current_file]
+       );
+}
+
+static void play_post_select(struct sched *s, struct task *t)
+{
+       struct play_task *pt = container_of(t, struct play_task, task);
+       int ret;
+
+       ret = eof_cleanup(pt);
+       if (ret < 0) {
+               pt->rq = CRT_TERM_RQ;
+               return;
+       }
+       ret = session_post_select(s, t);
+       if (ret < 0)
+               goto out;
+       if (!pt->wn.btrn && !pt->fn.btrn) {
+               char state = get_playback_state(pt);
+               if (state == 'P' || state == 'R' || state == 'F') {
+                       PARA_NOTICE_LOG("state: %c\n", state);
+                       ret = load_next_file(pt);
+                       if (ret < 0) {
+                               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+                               pt->rq = CRT_TERM_RQ;
+                               ret = 1;
+                               goto out;
+                       }
+                       pt->next_update = *now;
+               }
+       }
+       if (tv_diff(now, &pt->next_update, NULL) >= 0) {
+               char *str;
+               unsigned len = get_time_string(pt, &str);
+               struct timeval delay = {.tv_sec = 0, .tv_usec = 100 * 1000};
+               if (str && len > 0)
+                       session_update_time_string(pt, str, len);
+               free(str);
+               tv_add(now, &delay, &pt->next_update);
+       }
+       ret = 1;
+out:
+       t->error = ret;
+}
+
+/**
+ * The main function of para_play.
+ *
+ * \param argc Standard.
+ * \param argv Standard.
+ *
+ * \return \p EXIT_FAILURE or \p EXIT_SUCCESS.
+ */
+int main(int argc, char *argv[])
+{
+       int ret;
+       struct play_task *pt = &play_task;
+
+       /* needed this early to make help work */
+       recv_init();
+       filter_init();
+       writer_init();
+
+       gettimeofday(now, NULL);
+       sched.default_timeout.tv_sec = 5;
+
+       parse_config_or_die(argc, argv);
+       if (conf.inputs_num == 0)
+               print_help_and_die();
+       check_afh_receiver_or_die();
+
+       session_open(pt);
+       if (conf.randomize_given)
+               shuffle(conf.inputs, conf.inputs_num);
+       pt->invalid = para_calloc(sizeof(*pt->invalid) * conf.inputs_num);
+       pt->rq = CRT_FILE_CHANGE;
+       pt->current_file = conf.inputs_num - 1;
+       pt->playing = true;
+       pt->task.pre_select = play_pre_select;
+       pt->task.post_select = play_post_select;
+       sprintf(pt->task.status, "play task");
+       register_task(&sched, &pt->task);
+       ret = schedule(&sched);
+       if (ret < 0)
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/play.cmd b/play.cmd
new file mode 100644 (file)
index 0000000..459ad8c
--- /dev/null
+++ b/play.cmd
@@ -0,0 +1,77 @@
+BN: play
+SF: play.c
+SN: list of commands
+---
+N: help
+D: Display command list or help for given command.
+U: help [command]
+H: This command acts differently depending on whether it is executed in command
+H: mode or in insert mode.  In command mode, the list of keybindings is printed.
+H: In insert mode, if no command is given, the list of commands is shown.
+H: Otherwise, the help for the given command is printed.
+---
+N: next
+D: Load next file.
+U: next
+H: Closes the current file and loads the next file of the playlist.
+---
+N: prev
+D: Load previous file.
+U: prev
+H: Closes the current file and loads the previous file of the playlist.
+---
+N: fg
+D: Enter command mode.
+U: fg
+H: In this mode, file name and play time are displayed.  Hit CTRL+C to switch to
+H: input mode.
+---
+N: bg
+D: Enter input mode.
+U: bg
+H: Only useful if called in command mode via a key binding. The default key
+H: bindings map this command to the colon key, so pressing : in command mode
+H: activates insert mode.
+---
+N: jmp
+D: Jump to position in current file.
+U: jmp <percent>
+H: The <percent> argument should be an integer between 0 and 100.
+---
+N: ff
+D: Jump forwards or backwards.
+U: ff <seconds>
+H: Negative values mean to jmp backwards the given amount of seconds.
+---
+N: ls
+D: List playlist.
+U: ls
+H: This prints all paths of the playlist. The currently active file is
+H: marked with an asterisk.
+---
+N: info
+D: Print information about the current file.
+U: info
+H: This is the audio file selector info.
+---
+N: play
+D: Start or resume playing.
+U: play [<num>]
+H: Without <num>, starts playing at the current position. Otherwise, the
+H: corresponding file is loaded and playback is started.
+---
+N: pause
+D: Stop playing.
+U: pause
+H: When paused, it is still possible to jump around in the file via the jmp and ff
+H: comands.
+---
+N: tasks
+D: Print list of active tasks.
+U: tasks
+H: Mainly useful for debugging.
+---
+N: quit
+D: Exit para_play.
+U: quit
+H: Pressing CTRL+D causes EOF on stdin which also exits para_play.
diff --git a/recv.c b/recv.c
index c021b17bdbefdbcc7e562f496b6ae14917d3d681..a37e4d08dc0be08d1c092cfd3156b5b413de9d3b 100644 (file)
--- a/recv.c
+++ b/recv.c
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "recv.h"
 #include "recv.cmdline.h"
 #include "fd.h"
 #include "string.h"
 #include "error.h"
 #include "stdout.h"
-#include "buffer_tree.h"
 #include "version.h"
 
+extern void afh_recv_init(struct receiver *r);
+#undef AFH_RECEIVER
+#define AFH_RECEIVER {.name = "afh", .init = afh_recv_init},
+DEFINE_RECEIVER_ARRAY;
+
 /** The gengetopt args info struct. */
 static struct recv_args_info conf;
 
diff --git a/recv.h b/recv.h
index c69520e402905524ebcb120bc44221f371d29371..1f3ecfc46540fbabaf6fba25d6c949bd468eb41d 100644 (file)
--- a/recv.h
+++ b/recv.h
@@ -119,6 +119,13 @@ struct receiver {
 
        /** The two help texts of this receiver. */
        struct ggo_help help;
+       /**
+        * Answer a buffer tree query.
+        *
+        * This optional function pointer is used for inter node communications
+        * of the buffer tree nodes. See \ref btr_command_handler for details.
+        */
+       btr_command_handler execute;
 };
 
 /** Define an array of all available receivers. */
@@ -126,6 +133,7 @@ struct receiver {
        HTTP_RECEIVER \
        DCCP_RECEIVER \
        UDP_RECEIVER \
+       AFH_RECEIVER \
        {.name = NULL}};
 
 /** Iterate over all available receivers. */
@@ -143,6 +151,7 @@ extern void dccp_recv_init(struct receiver *r);
 #define DCCP_RECEIVER {.name = "dccp", .init = dccp_recv_init},
 extern void udp_recv_init(struct receiver *r);
 #define UDP_RECEIVER {.name = "udp", .init = udp_recv_init},
+#define AFH_RECEIVER /* not active by default */
 
 extern struct receiver receivers[];
 /** \endcond receiver */
index 7d7d2b059cd4765dab811d3ace2ea144d4d8f7c8..20b6783218800a3be6d60d1efb8f2252e138a582 100644 (file)
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "recv.h"
 #include "string.h"
-#include "buffer_tree.h"
-
-DEFINE_RECEIVER_ARRAY;
 
 /**
  * Call the init function of each paraslash receiver.
diff --git a/sched.c b/sched.c
index 2b54ce194fae897842ac94b39ff597828c8de8c4..ae82b23338284d9278c1fffd90c93460b65bd6da 100644 (file)
--- a/sched.c
+++ b/sched.c
@@ -55,6 +55,10 @@ static void sched_preselect(struct sched *s)
                        unregister_task(t);
                        continue;
                }
+               if (t->notification != 0) {
+                       sched_min_delay(s);
+                       break;
+               }
                if (!t->pre_select)
                        continue;
                t->pre_select(s, t);
@@ -91,16 +95,9 @@ static void sched_post_select(struct sched *s)
                if (t->error >= 0)
                        call_post_select(s, t);
 //             PARA_INFO_LOG("%s: %d\n", t->status, t->ret);
+               t->notification = 0;
                if (t->error >= 0)
                        continue;
-               /*
-                * We have to check whether the list is empty because the call
-                * to ->post_select() might have called sched_shutdown(). In
-                * this case t has been unregistered already, so we must not
-                * unregister it again.
-                */
-               if (list_empty(&s->post_select_list))
-                       return;
                unregister_task(t);
        }
 }
@@ -175,6 +172,7 @@ again:
 void register_task(struct sched *s, struct task *t)
 {
        PARA_INFO_LOG("registering %s (%p)\n", t->status, t);
+       t->notification = 0;
        if (!s->pre_select_list.next)
                INIT_LIST_HEAD(&s->pre_select_list);
        if (!s->post_select_list.next)
@@ -189,29 +187,6 @@ void register_task(struct sched *s, struct task *t)
        }
 }
 
-/**
- * Unregister all tasks.
- *
- * \param s The scheduler instance to shut down.
- *
- * This will cause \a schedule() to return immediately because both the
- * \a pre_select_list and the \a post_select_list are empty. This function
- * must be called from the post_select (rather than the pre_select) method.
- */
-void sched_shutdown(struct sched *s)
-{
-       struct task *t, *tmp;
-
-       list_for_each_entry_safe(t, tmp, &s->pre_select_list, pre_select_node) {
-               t->error = -E_SCHED_SHUTDOWN;
-               unregister_task(t);
-       }
-       list_for_each_entry_safe(t, tmp, &s->post_select_list, post_select_node) {
-               t->error = -E_SCHED_SHUTDOWN;
-               unregister_task(t);
-       }
-}
-
 /**
  * Get the list of all registered tasks.
  *
@@ -245,6 +220,69 @@ char *get_task_list(struct sched *s)
        return msg;
 }
 
+/**
+ * Set the notification value of a task.
+ *
+ * \param t The task to notify.
+ * \param err A positive error code.
+ *
+ * Tasks which honor notifications are supposed to call \ref
+ * task_get_notification() in their post_select function and act on the
+ * returned notification value.
+ *
+ * If the scheduler detects during its pre_select loop that at least one task
+ * has been notified, the loop terminates, and the post_select methods of all
+ * taks are immediately called again.
+ *
+ * The notification for a task is reset after the call to its post_select
+ * method.
+ *
+ * \sa \ref task_get_notification().
+ */
+void task_notify(struct task *t, int err)
+{
+       assert(err > 0);
+       if (t->notification == -err) /* ignore subsequent notifications */
+               return;
+       PARA_INFO_LOG("notifying task %s: %s\n", t->status, para_strerror(err));
+       t->notification = -err;
+}
+
+/**
+ * Return the notification value of a task.
+ *
+ * \param t The task to get the notification value from.
+ *
+ * \return The notification value. If this is negative, the task has been
+ * notified by another task. Tasks are supposed to check for notifications by
+ * calling this function from their post_select method.
+ *
+ * \sa \ref task_notify().
+ */
+int task_get_notification(struct task *t)
+{
+       return t->notification;
+}
+
+/**
+ * Set the notification value of all tasks of a scheduler instance.
+ *
+ * \param s The scheduler instance whose tasks should be notified.
+ * \param err A positive error code.
+ *
+ * This simply iterates over all existing tasks of \a s and sets each
+ * task's notification value to \p -err.
+ */
+void task_notify_all(struct sched *s, int err)
+{
+       struct task *t;
+
+       list_for_each_entry(t, &s->pre_select_list, pre_select_node)
+               task_notify(t, err);
+       list_for_each_entry(t, &s->post_select_list, post_select_node)
+               task_notify(t, err);
+}
+
 /**
  * Set the select timeout to the minimal possible value.
  *
diff --git a/sched.h b/sched.h
index 49c1c085d9e09c4d847ce3002ab641c7a265689a..74c3fc650650fdb6b9d49e7dd500155a9d1e3d39 100644 (file)
--- a/sched.h
+++ b/sched.h
@@ -68,6 +68,8 @@ struct task {
        struct list_head post_select_node;
        /** Descriptive text and current status of the task. */
        char status[255];
+       /** If less than zero, the task was notified by another task. */
+       int notification;
 };
 
 /**
@@ -81,7 +83,9 @@ extern struct timeval *now;
 void register_task(struct sched *s, struct task *t);
 int schedule(struct sched *s);
 char *get_task_list(struct sched *s);
-void sched_shutdown(struct sched *s);
+void task_notify(struct task *t, int err);
+void task_notify_all(struct sched *s, int err);
+int task_get_notification(struct task *t);
 void sched_min_delay(struct sched *s);
 void sched_request_timeout(struct timeval *to, struct sched *s);
 void sched_request_timeout_ms(long unsigned ms, struct sched *s);
index 8e8cb5fdcdd31e012137a83a4661e2728ac345e4..5d4a9fe762b2d6ff1936cb224ae06b33e9be5729 100644 (file)
--- a/server.c
+++ b/server.c
@@ -14,7 +14,7 @@
  *
  *
  *     - The main programs: \ref server.c, \ref audiod.c, \ref client.c,
- *       \ref audioc.c, \ref afh.c
+ *       \ref audioc.c, \ref afh.c, \ref play.c,
  *     - Server: \ref server_command, \ref sender,
  *     - Audio file selector: \ref audio_format_handler, \ref afs_table,
  *     - Client: \ref receiver, \ref receiver_node, \ref filter,
index 615553c586064ed39bfb29e31c3c8fff18faf852..ec188b5e165a0cdb57d5a8aed009286ea1ec532f 100644 (file)
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
+#include "buffer_tree.h"
 #include "recv.h"
 #include "udp_recv.cmdline.h"
 #include "string.h"
 #include "net.h"
 #include "fd.h"
-#include "buffer_tree.h"
 
 static void udp_recv_pre_select(struct sched *s, struct task *t)
 {
@@ -57,7 +57,9 @@ static void udp_recv_post_select(__a_unused struct sched *s, struct task *t)
        struct iovec iov[2];
        int ret, readv_ret, iovcnt;
 
-       t->error = 0;
+       ret = task_get_notification(t);
+       if (ret < 0)
+               goto out;
        ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
        if (ret <= 0)
                goto out;
index 56ee3469fbe666dda6ef470644ec18c3b86715e2..3ebeb6b04f82e6907ce1faabb14e74b56883485e 100644 (file)
@@ -145,6 +145,12 @@ interactive sessions on systems with libreadline.
 
 A command line HTTP/DCCP/UDP stream grabber. The http mode is
 compatible with arbitrary HTTP streaming sources (e.g. icecast).
+In addition to the three network streaming modes, para_recv can also
+operate in local (afh) mode. In this mode it writes the content of
+an audio file on the local file system in complete chunks to stdout,
+optionally 'just in time'. This allows to cut an audio file without
+first decoding it, and it enables third-party software which is unaware
+of the particular audio format to send complete frames in real time.
 
 *para_filter*
 
@@ -159,11 +165,7 @@ for audio volume.
 
 A small stand-alone program that prints tech info about the given
 audio file to STDOUT. It can be instructed to print a "chunk table",
-an array of offsets within the audio file or to write the content of
-the audio file in complete chunks 'just in time'.
-
-This allows third-party streaming software that is unaware of the
-particular audio format to send complete frames in real time.
+an array of offsets within the audio file.
 
 *para_write*
 
@@ -172,6 +174,9 @@ output plug-in and optional WAV/raw players for ALSA (Linux) and for
 coreaudio (Mac OS). para_write can also be used as a stand-alone WAV
 or raw audio player.
 
+*para_play*
+
+A command line audio player.
 
 *para_gui*
 
@@ -272,7 +277,8 @@ Optional:
 
        - XREFERENCE(http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html,
        GNU Readline). If this library (libreadline-dev) is installed,
-       para_client and para_audioc support interactive sessions.
+       para_client, para_audioc and para_play support interactive
+       sessions.
 
 Installation
 ~~~~~~~~~~~~