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/icy_server.c | 147 ++++++++++++++++++++++++++++++ src/icy_server.h | 39 ++++++++ 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 +++++++- 6 files changed, 409 insertions(+), 12 deletions(-) create mode 100644 src/icy_server.c create mode 100644 src/icy_server.h (limited to 'src') diff --git a/src/icy_server.c b/src/icy_server.c new file mode 100644 index 000000000..f927119f1 --- /dev/null +++ b/src/icy_server.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "icy_server.h" + +#include + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "icy_server" + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint) +{ + return g_strdup_printf("ICY 200 OK\r\n" + "icy-notice1:
This stream requires an audio player!
\r\n" /* TODO */ + "icy-notice2:MPD - The music player daemon
\r\n" + "icy-name: %s\r\n" /* TODO */ + "icy-genre: %s\r\n" /* TODO */ + "icy-url: %s\r\n" /* TODO */ + "icy-pub:1\r\n" + "icy-metaint:%d\r\n" + /* TODO "icy-br:%d\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", + name, + genre, + url, + metaint, + /* bitrate, */ + content_type); +} + +char* +icy_server_metadata_string(const char *stream_title, const char* stream_url) +{ + gchar *icy_metadata; + guint meta_length; + + // The leading n is a placeholder for the length information + icy_metadata = g_strdup_printf("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); + + g_return_val_if_fail(icy_metadata, NULL); + + meta_length = strlen(icy_metadata); + + meta_length--; // substract placeholder + + meta_length = ((int)meta_length / 16) + 1; + + icy_metadata[0] = meta_length; + + if (meta_length > 255) { + g_free(icy_metadata); + return NULL; + } + + return icy_metadata; +} + +struct page* +icy_server_metadata_page(const struct tag *tag, ...) +{ + va_list args; + const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; + gint last_item, item; + guint position; + gchar *icy_string; + struct page *icy_metadata; + gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - + // "StreamTitle='';StreamUrl='';" + // = 4081 - 28 + + last_item = -1; + + va_start(args, tag); + while (1) { + enum tag_type type; + const gchar *tag_item; + + type = va_arg(args, enum tag_type); + + if (type == TAG_NUM_OF_ITEM_TYPES) + break; + + tag_item = tag_get_value(tag, type); + + if (tag_item) + tag_items[++last_item] = tag_item; + } + va_end(args); + + position = item = 0; + while (position < sizeof(stream_title) && item <= last_item) { + gint length = 0; + + length = g_strlcpy(stream_title + position, + tag_items[item++], + sizeof(stream_title) - position); + + position += length; + + if (item <= last_item) { + length = g_strlcpy(stream_title + position, + " - ", + sizeof(stream_title) - position); + + position += length; + } + } + + icy_string = icy_server_metadata_string(stream_title, ""); + + if (icy_string == NULL) + return NULL; + + icy_metadata = page_new_copy(icy_string, (icy_string[0] * 16) + 1); + + g_free(icy_string); + + return icy_metadata; +} diff --git a/src/icy_server.h b/src/icy_server.h new file mode 100644 index 000000000..b3b4a5cfa --- /dev/null +++ b/src/icy_server.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef ICY_SERVER_H +#define ICY_SERVER_H + +#include "page.h" +#include "tag.h" + +#include + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint); + +char* +icy_server_metadata_string(const char *stream_title, const char* stream_url); + +struct page* +icy_server_metadata_page(const struct tag *tag, ...); + +#endif 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