From 92ba754fc67b084202c1a1c7a750fa4f2133418c Mon Sep 17 00:00:00 2001 From: Hagen Schink Date: Mon, 13 Apr 2009 19:35:02 +0200 Subject: Implemented basic icy support for the httpd output [mk: folded with patch "Put icy related functions in extra source files"; moved icy_server.c from HAVE_CURL to ENABLE_HTTPD_OUTPUT; removed an unused variable] --- src/output/httpd_client.c | 187 ++++++++++++++++++++++++++++++++++++--- src/output/httpd_client.h | 6 ++ src/output/httpd_internal.h | 5 ++ src/output/httpd_output_plugin.c | 37 +++++++- 4 files changed, 223 insertions(+), 12 deletions(-) (limited to 'src/output') diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 266a207cc..efdb9b190 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -21,6 +21,7 @@ #include "httpd_internal.h" #include "fifo_buffer.h" #include "page.h" +#include "icy_server.h" #include #include @@ -85,6 +86,39 @@ struct httpd_client { * #current_page. */ size_t current_position; + + /* ICY */ + + /** + * If we should sent icy metadata. + */ + gboolean metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + gboolean metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + guint metaint; + + /** + * The metadata as #page which is currently being sent to the client. + */ + struct page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + guint metadata_fill; }; static void @@ -110,6 +144,9 @@ httpd_client_free(struct httpd_client *client) } else fifo_buffer_free(client->input); + if (client->metadata) + page_unref (client->metadata); + g_source_remove(client->read_source_id); g_io_channel_unref(client->channel); g_free(client); @@ -171,6 +208,12 @@ httpd_client_handle_line(struct httpd_client *client, const char *line) return true; } + if (strncmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + client->metadata_requested = TRUE; + return true; + } + /* expect more request headers */ return true; } @@ -218,18 +261,33 @@ httpd_client_send_response(struct httpd_client *client) assert(client->state == RESPONSE); - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); + if (!client->metadata_requested) { + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + client->httpd->content_type); + } else { + gchar *metadata_header; + + metadata_header = icy_server_metadata_header("Add config information here!", /* TODO */ + "Add config information here!", /* TODO */ + "Add config information here!", /* TODO */ + client->httpd->content_type, + client->metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + } status = g_io_channel_write_chars(client->channel, buffer, strlen(buffer), &bytes_written, &error); + switch (status) { case G_IO_STATUS_NORMAL: case G_IO_STATUS_AGAIN: @@ -385,6 +443,13 @@ httpd_client_new(struct httpd_output *httpd, int fd) client->input = fifo_buffer_new(4096); client->state = REQUEST; + client->metadata_requested = FALSE; + client->metadata_sent = TRUE; + client->metaint = 8192; /*TODO: just a std value */ + client->metadata = NULL; + client->metadata_current_position = 0; + client->metadata_fill = 0; + return client; } @@ -444,6 +509,42 @@ write_page_to_channel(GIOChannel *channel, bytes_written_r, error); } +static GIOStatus +write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, + size_t position, gint n, + gsize *bytes_written_r, GError **error) +{ + GIOStatus status; + + assert(channel != NULL); + assert(page != NULL); + assert(position < page->size); + + if (n == -1) { + status = write_page_to_channel (channel, page, position, + bytes_written_r, error); + } else { + status = g_io_channel_write_chars(channel, + (const gchar*)page->data + position, + n, bytes_written_r, error); + } + + return status; +} + +static gint +bytes_left_till_metadata (struct httpd_client *client) +{ + assert(client != NULL); + + if (client->metadata_requested && + client->current_page->size - client->current_position + > client->metaint - client->metadata_fill) + return client->metaint - client->metadata_fill; + + return -1; +} + static gboolean httpd_client_out_event(GIOChannel *source, G_GNUC_UNUSED GIOCondition condition, gpointer data) @@ -453,6 +554,7 @@ httpd_client_out_event(GIOChannel *source, GError *error = NULL; GIOStatus status; gsize bytes_written; + gint bytes_to_write; g_mutex_lock(httpd->mutex); @@ -471,14 +573,62 @@ httpd_client_out_event(GIOChannel *source, client->current_position = 0; } - status = write_page_to_channel(source, client->current_page, - client->current_position, - &bytes_written, &error); + bytes_to_write = bytes_left_till_metadata(client); + + if (bytes_to_write == 0) { + gint metadata_to_write; + + metadata_to_write = client->metadata_current_position; + + if (!client->metadata_sent) { + status = write_page_to_channel(source, + client->metadata, + metadata_to_write, + &bytes_written, &error); + + client->metadata_current_position += bytes_written; + + if (client->metadata->size + - client->metadata_current_position == 0) { + client->metadata_fill = 0; + client->metadata_current_position = 0; + client->metadata_sent = TRUE; + } + } else { + struct page *empty_meta; + guchar empty_data = 0; + + empty_meta = page_new_copy(&empty_data, 1); + + status = write_page_to_channel(source, + empty_meta, + metadata_to_write, + &bytes_written, &error); + + client->metadata_current_position += bytes_written; + + if (empty_meta->size + - client->metadata_current_position == 0) { + client->metadata_fill = 0; + client->metadata_current_position = 0; + } + } + + bytes_written = 0; + } else { + status = write_n_bytes_to_channel(source, client->current_page, + client->current_position, bytes_to_write, + &bytes_written, &error); + } + switch (status) { case G_IO_STATUS_NORMAL: client->current_position += bytes_written; assert(client->current_position <= client->current_page->size); + if (client->metadata_requested) + client->metadata_fill += bytes_written; + if (client->current_position >= client->current_page->size) { page_unref(client->current_page); client->current_page = NULL; @@ -539,3 +689,18 @@ httpd_client_send(struct httpd_client *client, struct page *page) g_io_add_watch(client->channel, G_IO_OUT, httpd_client_out_event, client); } + +void +httpd_client_send_metadata(struct httpd_client *client, struct page *page) +{ + if (client->metadata) { + page_unref(client->metadata); + client->metadata = NULL; + } + + g_return_if_fail (page); + + page_ref(page); + client->metadata = page; + client->metadata_sent = FALSE; +} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h index c81cdb12f..80ab6bc2e 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -62,4 +62,10 @@ httpd_client_cancel(struct httpd_client *client); void httpd_client_send(struct httpd_client *client, struct page *page); +/** + * Sends the passed metadata. + */ +void +httpd_client_send_metadata(struct httpd_client *client, struct page *page); + #endif diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index d19909c83..2257e27a2 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -81,6 +81,11 @@ struct httpd_output { */ struct page *header; + /** + * The metadata, which is sent to every client. + */ + struct page *metadata; + /** * A linked list containing all clients which are currently * connected. diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 60d17c520..c1f609489 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -24,6 +24,7 @@ #include "encoder_list.h" #include "socket_util.h" #include "page.h" +#include "icy_server.h" #include @@ -83,6 +84,9 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, sin->sin_addr.s_addr = INADDR_ANY; httpd->address_size = sizeof(*sin); + /* initialize metadata */ + httpd->metadata = NULL; + /* initialize encoder */ httpd->encoder = encoder_init(encoder_plugin, param, error); @@ -99,6 +103,9 @@ httpd_output_finish(void *data) { struct httpd_output *httpd = data; + if (httpd->metadata) + page_unref(httpd->metadata); + encoder_finish(httpd->encoder); g_mutex_free(httpd->mutex); g_free(httpd); @@ -114,6 +121,10 @@ httpd_client_add(struct httpd_output *httpd, int fd) struct httpd_client *client = httpd_client_new(httpd, fd); httpd->clients = g_list_prepend(httpd->clients, client); + + /* pass metadata to client */ + if (httpd->metadata) + httpd_client_send_metadata(client, httpd->metadata); } static gboolean @@ -353,12 +364,36 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error) return size; } +static void +httpd_send_metadata(gpointer data, gpointer user_data) +{ + struct httpd_client *client = data; + struct page *icy_metadata = user_data; + + httpd_client_send_metadata(client, icy_metadata); +} + static void httpd_output_tag(void *data, const struct tag *tag) { struct httpd_output *httpd = data; - /* XXX add suport for icy-metadata */ + if (httpd->metadata) { + page_unref (httpd->metadata); + httpd->metadata = NULL; + } + + if (tag) + httpd->metadata = icy_server_metadata_page(tag, TAG_ITEM_ALBUM, + TAG_ITEM_ARTIST, + TAG_ITEM_TITLE, + TAG_NUM_OF_ITEM_TYPES); + + if (httpd->metadata) { + g_mutex_lock(httpd->mutex); + g_list_foreach(httpd->clients, httpd_send_metadata, httpd->metadata); + g_mutex_unlock(httpd->mutex); + } encoder_tag(httpd->encoder, tag, NULL); } -- cgit v1.2.3