From 5e3641e5e692c3f2f644a4bb809c88727cb8bee9 Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Fri, 8 Nov 2024 14:53:40 -0500 Subject: Command queue for portal and list, work on server Most of the server stuff can undoubtedly be simplified. I'm still working that out. Signed-off-by: Andrew Opalach --- src/liana/client.c | 3 +- src/liana/handlers/cdio_server.c | 12 +- src/liana/list.c | 373 +++++++++++++++++++++++++++++++-------- src/liana/list.h | 26 ++- src/liana/server.c | 42 +++-- src/liana/server.h | 16 +- 6 files changed, 374 insertions(+), 98 deletions(-) (limited to 'src/liana') diff --git a/src/liana/client.c b/src/liana/client.c index 55a925a..527376a 100644 --- a/src/liana/client.c +++ b/src/liana/client.c @@ -60,6 +60,7 @@ static void parse_info_packet(struct lia_client *client, struct aki_packet *pack break; case AVMEDIA_TYPE_VIDEO: client->mask |= 1 << index; + //continue; break; case AVMEDIA_TYPE_SUBTITLE: default: @@ -106,7 +107,7 @@ static void info_packet_callback(void *userdata, struct aki_packet_stream *strea struct lia_client *client = (struct lia_client *)userdata; client->connection_id = aki_packet_read_u16(packet); parse_info_packet(client, packet); - al_assert(client->mask != 0); + //al_assert(client->mask != 0); aki_packet_free(packet); stream->packet_callback = data_packet_callback; struct aki_packet *rpacket = aki_packet_create(); diff --git a/src/liana/handlers/cdio_server.c b/src/liana/handlers/cdio_server.c index 118f6b5..d271829 100644 --- a/src/liana/handlers/cdio_server.c +++ b/src/liana/handlers/cdio_server.c @@ -45,14 +45,20 @@ static void cdio_server_subscribe(struct lia_server_handler *handler, s32 mask) static u64 cdio_server_get_duration(struct lia_server_handler *handler) { - (void)handler; - return 0; + struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler; + struct cch_chapter *first = &al_array_at(cdio->handle->entry->chapters, 0); + struct cch_chapter *last = &al_array_last(cdio->handle->entry->chapters); + f64 seconds = camu_audio_format_bytes_to_sec(&cdio->fmt, (last->end - first->start) * CDIO_CD_FRAMESIZE_RAW); + al_log_info("cdio", "Length: %.2fs.", seconds); + return (u64)(seconds * 1000000.0); } static bool cdio_server_seek(struct lia_server_handler *handler, u64 pos) { struct lia_cdio_server *cdio = (struct lia_cdio_server *)handler; - return cch_handle_seek(cdio->handle, pos, SEEK_SET); + (void)cdio; + (void)pos; + return false; } static void cdio_server_step(struct lia_server_handler *handler) diff --git a/src/liana/list.c b/src/liana/list.c index 970d247..fc34aca 100644 --- a/src/liana/list.c +++ b/src/liana/list.c @@ -6,16 +6,6 @@ #include "list.h" #include "list_cmp.h" -void lia_list_init(struct lia_list *list) -{ - list->current = -1; - list->previous = -1; - list->queued = -1; - list->idle = true; - al_array_init(list->entries); - al_array_init(list->sinks); -} - /* static void buffer_ahead(struct lia_list *list) { @@ -33,13 +23,33 @@ static void buffer_ahead(struct lia_list *list) } */ -static void unset_all(struct lia_list *list) +enum { + ADD_SINK = 0, + REMOVE_SINK, + ADD, + UNSET, + SKIPTO, + SKIP, + TOGGLE_PAUSE, + SEEK, + END, + REVERSE, + SORT, + SHUFFLE, + CLEAR +}; + +void lia_list_init(struct lia_list *list, str *name) { - struct lia_list_sink *sink; - al_array_foreach(list->sinks, i, sink) { - sink->set = -1; - sink->queued = -1; - } + al_str_clone(&list->name, name); + list->current = -1; + list->previous = -1; + list->queued = -1; + list->idle = true; + al_array_init(list->entries); + al_array_init(list->sinks); + al_array_init(list->queue); + list->cmd = NULL; } static bool assume_ended(struct lia_list_entry *entry, u64 at) @@ -57,15 +67,25 @@ static bool assume_ended(struct lia_list_entry *entry, u64 at) return false; } -void lia_list_add_sink(struct lia_list *list, void (*callback)(void *, u8, struct lia_list_entry *, s32, struct lia_timing *), void *userdata) +static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_entry *entry) +{ + bool loaded; + list->callback(list->userdata, LIANA_LOAD_ENTRY, entry, &loaded); + if (loaded) { + list->callback(list->userdata, LIANA_GET_DURATION, entry, &entry->duration); + return true; + } + return false; +} + +static bool handle_add_sink(struct lia_list *list, struct lia_list_sink *sink) { - struct lia_list_sink *sink = al_alloc_object(struct lia_list_sink); - sink->callback = callback; - sink->userdata = userdata; - al_array_push(list->sinks, sink); if (list->current >= 0) { - sink->set = list->current; struct lia_list_entry *current = al_array_at(list->entries, list->current); + if (!entry_load_and_get_duration(list, current)) { + return false; + } + sink->set = list->current; u64 now = aki_get_timestamp(); u8 pause; u64 at = LIANA_TIMESTAMP_INVALID; @@ -92,9 +112,11 @@ void lia_list_add_sink(struct lia_list *list, void (*callback)(void *, u8, struc sink->set = -1; } sink->queued = -1; + al_array_push(list->sinks, sink); + return true; } -void lia_list_remove_sink(struct lia_list *list, void *userdata) +static void handle_remove_sink(struct lia_list *list, void *userdata) { struct lia_list_sink *sink; al_array_foreach(list->sinks, i, sink) { @@ -106,18 +128,12 @@ void lia_list_remove_sink(struct lia_list *list, void *userdata) } } -void lia_list_add(struct lia_list *list, void *opaque, u64 duration, str *name) +static bool handle_add(struct lia_list *list, struct lia_list_entry *entry) { - struct lia_list_entry *entry = al_alloc_object(struct lia_list_entry); - entry->opaque = opaque; - entry->paused_at = LIANA_TIMESTAMP_INVALID; - entry->held = false; - entry->offset = 0; - entry->ended = false; - entry->duration = duration; - al_str_clone(&entry->name, name); - al_array_push(list->entries, entry); if (list->idle) { + if (!entry_load_and_get_duration(list, entry)) { + return false; + } list->current++; list->idle = false; entry->start = aki_get_timestamp() + LIANA_BASE_DELAY; @@ -133,10 +149,11 @@ void lia_list_add(struct lia_list *list, void *opaque, u64 duration, str *name) sink->set = list->current; sink->callback(sink->userdata, LIANA_SINK_SET, entry, list->current, &time); } - if (list->callback) list->callback(list->userdata, LIANA_META_PLAYING, entry); + // meta playing } else { /* if (list->queued == -1) { + // TODODODO: this is based on addeding entry to list->entries BEFORE this point. struct lia_list_entry *current = al_array_at(list->entries, list->current); list->queued = list->current + 1; entry->start = current->start + (current->duration - current->offset); @@ -154,11 +171,22 @@ void lia_list_add(struct lia_list *list, void *opaque, u64 duration, str *name) */ entry->start = LIANA_TIMESTAMP_INVALID; //} - if (list->callback) list->callback(list->userdata, LIANA_META_QUEUED, entry); + // meta queued } + al_array_push(list->entries, entry); + return true; } -void lia_list_unset(struct lia_list *list) +static void unset_all(struct lia_list *list) +{ + struct lia_list_sink *sink; + al_array_foreach(list->sinks, i, sink) { + sink->set = -1; + sink->queued = -1; + } +} + +static void handle_unset(struct lia_list *list) { unset_all(list); list->current = list->entries.size - 1; @@ -178,39 +206,40 @@ static struct lia_list_entry *get_entry_from_sequence(struct lia_list *list, s32 return al_array_at(list->entries, sequence); } - -void lia_list_skipto(struct lia_list *list, s32 sequence, s32 index) +// TODO: +// - Think about what is means for an entry to be done. never put into a pause state? +// - clock_end()?? +// - Sink needs to handle case where entry gets queued but the list already expects it to be playing +// - It's possible to know if sink->current is done during a set command, synchronously. +// So, check that when queueing an entry. +// - Can clock be ended during a queue command in any other case? +// - In the simplest case of our only operation being skip, how could client's become desynced? +// - Then with toggle pause +// - Is it safe to assert paused state on the client. +// - Do queued +// - Do seek +//if (current->start != LIANA_TIMESTAMP_INVALID && current->start > ts - LIANA_BASE_PING) { + +static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) { - if (index == list->current) return; + if (index == list->current) return true; if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current; if (sequence == list->previous) { // This can happen but almost certainly won't be expected behavior. - return; + return true; } struct lia_list_entry *current = get_entry_from_sequence(list, sequence); struct lia_list_entry *target = get_entry_from_sequence(list, index); - if (!current || !target) return; + if (!current || !target) return true; + if (!entry_load_and_get_duration(list, target)) { + return false; + } u64 now = aki_get_timestamp(); u64 at = now + LIANA_BASE_DELAY; u8 pause; - // TODO: - // - Think about what is means for an entry to be done. never put into a pause state? - // - clock_end()?? - // - Sink needs to handle case where entry gets queued but the list already expects it to be playing - // - It's possible to know if sink->current is done during a set command, synchronously. - // So, check that when queueing an entry. - // - Can clock be ended during a queue command in any other case? - // - In the simplest case of our only operation being skip, how could client's become desynced? - // - Then with toggle pause - // - Is it safe to assert paused state on the client. - // - Do queued - // - Do seek - - //if (current->start != LIANA_TIMESTAMP_INVALID && current->start > ts - LIANA_BASE_PING) { - // This should only happen if `start` has never been set. if (target->start == LIANA_TIMESTAMP_INVALID && target->paused_at == LIANA_TIMESTAMP_INVALID) { target->start = at; @@ -264,16 +293,18 @@ void lia_list_skipto(struct lia_list *list, s32 sequence, s32 index) sink->callback(sink->userdata, LIANA_SINK_SET, target, index, &time); } - list->callback(list->userdata, LIANA_META_PLAYING, target); + // meta playing + + return true; } -void lia_list_skip(struct lia_list *list, s32 sequence, s32 n) +static bool handle_skip(struct lia_list *list, s32 sequence, s32 n) { if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current; - lia_list_skipto(list, sequence, sequence + n); + return handle_skipto(list, sequence, sequence + n); } -void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) +static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) { if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current; struct lia_list_entry *current = get_entry_from_sequence(list, sequence); @@ -284,7 +315,9 @@ void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) u64 at; switch (pause) { case LIANA_PAUSE_PAUSE: - al_assert(pts != -1.0); + // This assert should exist but the correct behavior for this is unfinished. + //al_assert(pts != -1.0); + (void)pts; current->paused_at = now + LIANA_PAUSE_DELAY; current->offset += current->paused_at - current->start; current->start = LIANA_TIMESTAMP_INVALID; @@ -310,9 +343,10 @@ void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) } } -void lia_list_seek(struct lia_list *list, s32 sequence, f64 percent) +static void handle_seek(struct lia_list *list, s32 sequence, f64 percent) { if (sequence == LIANA_SEQUENCE_ANY) sequence = list->current; + if (sequence < 0) return; list->idle = false; struct lia_list_entry *current = get_entry_from_sequence(list, sequence); u64 pos = (u64)(current->duration * percent); @@ -332,7 +366,7 @@ void lia_list_seek(struct lia_list *list, s32 sequence, f64 percent) } } -void lia_list_end(struct lia_list *list, s32 sequence) +static void handle_end(struct lia_list *list, s32 sequence) { al_assert(sequence != LIANA_SEQUENCE_ANY); if (sequence != list->current) return; @@ -353,11 +387,13 @@ void lia_list_end(struct lia_list *list, s32 sequence) al_array_foreach(list->sinks, i, sink) { sink->queued = -1; } - if (list->callback) { - list->callback(list->userdata, LIANA_META_PLAYING, al_array_at(list->entries, list->current)); - } + // meta playing } else if (next < size) { - lia_list_skipto(list, sequence, next); + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = SKIPTO; + cmd->sequence = sequence; + cmd->i = next; + al_array_push(list->queue, cmd); } else { list->idle = true; struct lia_list_sink *sink; @@ -367,7 +403,7 @@ void lia_list_end(struct lia_list *list, s32 sequence) } } -void lia_list_reverse(struct lia_list *list) +static void handle_reverse(struct lia_list *list) { u32 size = list->entries.size; for (u32 i = 0; i < size; i++) { @@ -378,13 +414,13 @@ void lia_list_reverse(struct lia_list *list) unset_all(list); } -void lia_list_sort(struct lia_list *list) +static void handle_sort(struct lia_list *list) { al_array_sort(list->entries, struct lia_list_entry *, camu_db_compare); unset_all(list); } -void lia_list_shuffle(struct lia_list *list) +static void handle_shuffle(struct lia_list *list) { u32 size = list->entries.size; if (size == 0) return; @@ -395,7 +431,7 @@ void lia_list_shuffle(struct lia_list *list) unset_all(list); } -void lia_list_clear(struct lia_list *list) +static void handle_clear(struct lia_list *list) { struct lia_list_entry *entry; al_array_foreach(list->entries, i, entry) { @@ -409,6 +445,200 @@ void lia_list_clear(struct lia_list *list) list->idle = true; } +static void pump_queue(struct lia_list *list) +{ + if (!list->cmd) { + if (list->queue.size == 0) return; + al_array_pop_at(list->queue, 0, list->cmd); + } + struct lia_list_cmd *cmd = list->cmd; + switch (cmd->op) { + case ADD_SINK: + if (!handle_add_sink(list, cmd->sink)) { + return; + } + break; + case REMOVE_SINK: + handle_remove_sink(list, cmd->userdata); + break; + case ADD: + if (!handle_add(list, cmd->entry)) { + return; + } + break; + case UNSET: + handle_unset(list); + break; + case SKIPTO: + if (!handle_skipto(list, cmd->sequence, cmd->i)) { + return; + } + break; + case SKIP: + if (!handle_skip(list, cmd->sequence, cmd->i)) { + return; + } + break; + case TOGGLE_PAUSE: + handle_toggle_pause(list, cmd->sequence, cmd->f); + break; + case SEEK: + handle_seek(list, cmd->sequence, cmd->f); + break; + case END: + handle_end(list, cmd->sequence); + break; + case REVERSE: + handle_reverse(list); + break; + case SORT: + handle_sort(list); + break; + case SHUFFLE: + handle_shuffle(list); + break; + case CLEAR: + handle_clear(list); + break; + } + al_free(cmd); + list->cmd = NULL; + pump_queue(list); +} + +void lia_list_pump(struct lia_list *list) +{ + pump_queue(list); +} + +void lia_list_add_sink(struct lia_list *list, void (*callback)(void *, u8, struct lia_list_entry *, s32, struct lia_timing *), void *userdata) +{ + struct lia_list_sink *sink = al_alloc_object(struct lia_list_sink); + sink->callback = callback; + sink->userdata = userdata; + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = ADD_SINK; + cmd->sink = sink; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_remove_sink(struct lia_list *list, void *userdata) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = REMOVE_SINK; + cmd->userdata = userdata; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_add(struct lia_list *list, void *opaque, u64 duration, str *name) +{ + struct lia_list_entry *entry = al_alloc_object(struct lia_list_entry); + entry->opaque = opaque; + entry->paused_at = LIANA_TIMESTAMP_INVALID; + entry->held = false; + entry->offset = 0; + entry->ended = false; + entry->duration = duration; + al_str_clone(&entry->name, name); + entry->list = list; + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = ADD; + cmd->entry = entry; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_unset(struct lia_list *list) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = UNSET; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_skipto(struct lia_list *list, s32 sequence, s32 index) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = SKIPTO; + cmd->sequence = sequence; + cmd->i = index; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_skip(struct lia_list *list, s32 sequence, s32 n) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = SKIP; + cmd->sequence = sequence; + cmd->i = n; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = TOGGLE_PAUSE; + cmd->sequence = sequence; + cmd->f = pts; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_seek(struct lia_list *list, s32 sequence, f64 percent) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = SEEK; + cmd->sequence = sequence; + cmd->f = percent; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_end(struct lia_list *list, s32 sequence) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = END; + cmd->sequence = sequence; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_reverse(struct lia_list *list) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = REVERSE; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_sort(struct lia_list *list) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = SORT; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_shuffle(struct lia_list *list) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = SHUFFLE; + al_array_push(list->queue, cmd); + pump_queue(list); +} + +void lia_list_clear(struct lia_list *list) +{ + struct lia_list_cmd *cmd = al_alloc_object(struct lia_list_cmd); + cmd->op = CLEAR; + al_array_push(list->queue, cmd); + pump_queue(list); +} + void lia_list_free(struct lia_list *list) { struct lia_list_entry *entry; @@ -422,4 +652,5 @@ void lia_list_free(struct lia_list *list) al_free(sink); } al_array_free(list->sinks); + al_str_free(&list->name); } diff --git a/src/liana/list.h b/src/liana/list.h index 5e3eaec..270ef98 100644 --- a/src/liana/list.h +++ b/src/liana/list.h @@ -25,14 +25,10 @@ enum { enum { LIANA_LOAD_ENTRY = 0, + LIANA_GET_DURATION, LIANA_UNLOAD_ENTRY }; -enum { - LIANA_META_PLAYING = 0, - LIANA_META_QUEUED -}; - // NOTE: To handle an entry being queued right before a skip, keep a global // "max time until all sinks buffered" and used that instead of LIANA_PAUSE_DELAY (if greater). @@ -61,6 +57,7 @@ struct lia_list_entry { bool ended; u64 duration; str name; + struct lia_list *list; }; struct lia_list_sink { @@ -70,18 +67,33 @@ struct lia_list_sink { void *userdata; }; +struct lia_list_cmd { + u8 op; + struct lia_list_sink *sink; + void *userdata; + struct lia_list_entry *entry; + s32 sequence; + s32 i; + f64 f; +}; + struct lia_list { + str name; s32 current; s32 previous; s32 queued; bool idle; array(struct lia_list_entry *) entries; array(struct lia_list_sink *) sinks; - void (*callback)(void *, u8, struct lia_list_entry *); + array(struct lia_list_cmd *) queue; + struct lia_list_cmd *cmd; + void (*callback)(void *, u8, struct lia_list_entry *, void *); void *userdata; }; -void lia_list_init(struct lia_list *list); +void lia_list_init(struct lia_list *list, str *name); + +void lia_list_pump(struct lia_list *list); void lia_list_add_sink(struct lia_list *list, void (*callback)(void *, u8, struct lia_list_entry *, s32, struct lia_timing *), void *userdata); void lia_list_remove_sink(struct lia_list *list, void *userdata); diff --git a/src/liana/server.c b/src/liana/server.c index 3eb303e..ccca0fd 100644 --- a/src/liana/server.c +++ b/src/liana/server.c @@ -289,23 +289,35 @@ struct lia_node *lia_server_create_node(struct lia_server *server, struct cch_en return node; } -u64 lia_node_get_duration(struct lia_node *node) +static aki_thread_result AKI_THREADCALL init_duration_thread(void *userdata) { - (void)node; -#if 0 - return 0; -#else - struct cch_handle handle; - cch_entry_get_handle(node->entry, &handle); - struct lia_server_handler *handler = lia_handler_by_name(cch_entry_get_liana(node->entry))->create_server_handler(); - if (!handler->init(handler, &handle)) { - return LIANA_TIMESTAMP_INVALID; + struct lia_node *node = (struct lia_node *)userdata; + if (!node->handler->init(node->handler, &node->handle)) { + node->errored = true; + } else { + node->duration = node->handler->get_duration(node->handler); } - u64 duration = handler->get_duration(handler); - handler->free(&handler); - cch_entry_return_handle(node->entry, &handle); - return duration; -#endif + aki_signal_send(&node->signal); + return 0; +} + +static void duration_signal_callback(void *userdata) +{ + struct lia_node *node = (struct lia_node *)userdata; + aki_signal_stop(&node->signal); + aki_thread_join(&node->thread); + node->handler->free(&node->handler); + cch_entry_return_handle(node->entry, &node->handle); + node->callback(node->userdata, LIANA_NODE_DURATION, node->duration); +} + +void lia_node_get_duration(struct lia_node *node) +{ + aki_signal_init(&node->signal, duration_signal_callback, node); + aki_signal_start(&node->signal, node->server->loop); + cch_entry_get_handle(node->entry, &node->handle); + node->handler = lia_handler_by_name(cch_entry_get_liana(node->entry))->create_server_handler(); + aki_thread_create(&node->thread, init_duration_thread, node); } void lia_server_close(struct lia_server *server) diff --git a/src/liana/server.h b/src/liana/server.h index 3c4c294..2ed7c12 100644 --- a/src/liana/server.h +++ b/src/liana/server.h @@ -21,11 +21,25 @@ struct lia_node_connection { struct lia_node *node; }; +enum { + LIANA_NODE_DURATION = 0 +}; + struct lia_node { u16 id; struct cch_entry *entry; array(struct lia_node_connection *) connections; struct lia_server *server; + u64 duration; + // Temporary copy from node_connection. We need to + // figure out a "connection pool" structure. + struct lia_server_handler *handler; + bool errored; + struct cch_handle handle; + struct aki_thread thread; + struct aki_signal signal; + void (*callback)(void *, u8, u64); + void *userdata; }; struct lia_server { @@ -37,6 +51,6 @@ struct lia_server { bool lia_server_init(struct lia_server *server, struct aki_event_loop *loop); void lia_server_add_socket(struct lia_server *server, struct aki_socket *sock); struct lia_node *lia_server_create_node(struct lia_server *server, struct cch_entry *entry); -u64 lia_node_get_duration(struct lia_node *node); +void lia_node_get_duration(struct lia_node *node); void lia_server_close(struct lia_server *server); void lia_server_free(struct lia_server *server); -- cgit v1.2.3-101-g0448