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]
This commit is contained in:
		 Hagen Schink
					Hagen Schink
				
			
				
					committed by
					
						 Max Kellermann
						Max Kellermann
					
				
			
			
				
	
			
			
			 Max Kellermann
						Max Kellermann
					
				
			
						parent
						
							200be26371
						
					
				
				
					commit
					92ba754fc6
				
			
							
								
								
									
										147
									
								
								src/icy_server.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/icy_server.c
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <glib.h> | ||||
|  | ||||
| #include <assert.h> | ||||
|  | ||||
| #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:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ | ||||
| 			       "icy-notice2:MPD - The music player daemon<BR>\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; | ||||
| } | ||||
							
								
								
									
										39
									
								
								src/icy_server.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/icy_server.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <stdarg.h> | ||||
|  | ||||
| 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 | ||||
| @@ -21,6 +21,7 @@ | ||||
| #include "httpd_internal.h" | ||||
| #include "fifo_buffer.h" | ||||
| #include "page.h" | ||||
| #include "icy_server.h" | ||||
|  | ||||
| #include <stdbool.h> | ||||
| #include <assert.h> | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -24,6 +24,7 @@ | ||||
| #include "encoder_list.h" | ||||
| #include "socket_util.h" | ||||
| #include "page.h" | ||||
| #include "icy_server.h" | ||||
|  | ||||
| #include <assert.h> | ||||
|  | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user