From d3b763a48c09a60a0c0b5ccb6cccd9376875c470 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Dec 2009 23:27:37 +0100
Subject: input_stream: return allocated input_stream objects

Major API redesign: don't let the caller allocate the input_stream
object.  Let each input plugin allocate its own (derived/extended)
input_stream pointer.  The "data" attribute can now be removed, and
all input plugins simply cast the input_stream pointer to their own
structure (with an "struct input_stream base" as the first attribute).
---
 src/archive/bz2_archive_plugin.c      | 35 +++++++------
 src/archive/iso9660_archive_plugin.c  | 27 +++++-----
 src/archive/zzip_archive_plugin.c     | 29 +++++------
 src/archive_plugin.c                  |  6 +--
 src/archive_plugin.h                  |  9 ++--
 src/decoder/wavpack_plugin.c          | 27 +++++-----
 src/decoder_thread.c                  | 42 +++++++--------
 src/input/archive_input_plugin.c      | 19 ++++---
 src/input/curl_input_plugin.c         | 97 +++++++++++++++++------------------
 src/input/file_input_plugin.c         | 44 +++++++++-------
 src/input/mms_input_plugin.c          | 27 +++++-----
 src/input/rewind_input_plugin.c       | 77 +++++++++++++--------------
 src/input/rewind_input_plugin.h       |  6 +--
 src/input_plugin.h                    |  3 +-
 src/input_stream.c                    | 24 ++++-----
 src/input_stream.h                    | 27 +++++-----
 src/playlist/lastfm_playlist_plugin.c | 44 ++++++++--------
 src/playlist_list.c                   |  6 ++-
 src/playlist_list.h                   |  4 +-
 src/playlist_queue.c                  | 21 ++++----
 src/song_update.c                     | 17 +++---
 test/dump_playlist.c                  | 21 ++++----
 test/read_tags.c                      |  8 +--
 test/run_decoder.c                    | 12 ++---
 test/run_input.c                      |  9 ++--
 25 files changed, 315 insertions(+), 326 deletions(-)

diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c
index 07d174403..75b4b1ece 100644
--- a/src/archive/bz2_archive_plugin.c
+++ b/src/archive/bz2_archive_plugin.c
@@ -45,10 +45,12 @@ struct bz2_archive_file {
 
 	char *name;
 	bool reset;
-	struct input_stream istream;
+	struct input_stream *istream;
 };
 
 struct bz2_input_stream {
+	struct input_stream base;
+
 	struct bz2_archive_file *archive;
 
 	bool eof;
@@ -111,7 +113,8 @@ bz2_open(const char *pathname, GError **error_r)
 	refcount_init(&context->ref);
 
 	//open archive
-	if (!input_stream_open(&context->istream, pathname, error_r)) {
+	context->istream = input_stream_open(pathname, error_r);
+	if (context->istream == NULL) {
 		g_free(context);
 		return NULL;
 	}
@@ -158,44 +161,42 @@ bz2_close(struct archive_file *file)
 
 	g_free(context->name);
 
-	input_stream_close(&context->istream);
+	input_stream_close(context->istream);
 	g_free(context);
 }
 
 /* single archive handling */
 
-static bool
-bz2_open_stream(struct archive_file *file, struct input_stream *is,
+static struct input_stream *
+bz2_open_stream(struct archive_file *file,
 		G_GNUC_UNUSED const char *path, GError **error_r)
 {
 	struct bz2_archive_file *context = (struct bz2_archive_file *) file;
 	struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1);
 
+	input_stream_init(&bis->base, &bz2_inputplugin);
+
 	bis->archive = context;
 
-	//setup file ops
-	is->plugin = &bz2_inputplugin;
-	//insert back reference
-	is->data = bis;
-	is->ready = true;
-	is->seekable = false;
+	bis->base.ready = true;
+	bis->base.seekable = false;
 
 	if (!bz2_alloc(bis, error_r)) {
 		g_free(bis);
-		return false;
+		return NULL;
 	}
 
 	bis->eof = false;
 
 	refcount_inc(&context->ref);
 
-	return true;
+	return &bis->base;
 }
 
 static void
 bz2_is_close(struct input_stream *is)
 {
-	struct bz2_input_stream *bis = (struct bz2_input_stream *)is->data;
+	struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
 
 	bz2_destroy(bis);
 
@@ -215,7 +216,7 @@ bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r)
 	if (bzstream->avail_in > 0)
 		return true;
 
-	count = input_stream_read(&bis->archive->istream,
+	count = input_stream_read(bis->archive->istream,
 				  bis->buffer, sizeof(bis->buffer),
 				  error_r);
 	if (count == 0)
@@ -230,7 +231,7 @@ static size_t
 bz2_is_read(struct input_stream *is, void *ptr, size_t length,
 	    GError **error_r)
 {
-	struct bz2_input_stream *bis = (struct bz2_input_stream *)is->data;
+	struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
 	bz_stream *bzstream;
 	int bz_result;
 	size_t nbytes = 0;
@@ -269,7 +270,7 @@ bz2_is_read(struct input_stream *is, void *ptr, size_t length,
 static bool
 bz2_is_eof(struct input_stream *is)
 {
-	struct bz2_input_stream *bis = (struct bz2_input_stream *)is->data;
+	struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
 
 	return bis->eof;
 }
diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c
index f6c6ed5f2..38003da94 100644
--- a/src/archive/iso9660_archive_plugin.c
+++ b/src/archive/iso9660_archive_plugin.c
@@ -163,14 +163,16 @@ iso9660_archive_close(struct archive_file *file)
 /* single archive handling */
 
 struct iso9660_input_stream {
+	struct input_stream base;
+
 	struct iso9660_archive_file *archive;
 
 	iso9660_stat_t *statbuf;
 	size_t max_blocks;
 };
 
-static bool
-iso9660_archive_open_stream(struct archive_file *file, struct input_stream *is,
+static struct input_stream *
+iso9660_archive_open_stream(struct archive_file *file,
 		const char *pathname, GError **error_r)
 {
 	struct iso9660_archive_file *context =
@@ -178,6 +180,7 @@ iso9660_archive_open_stream(struct archive_file *file, struct input_stream *is,
 	struct iso9660_input_stream *iis;
 
 	iis = g_new(struct iso9660_input_stream, 1);
+	input_stream_init(&iis->base, &iso9660_input_plugin);
 
 	iis->archive = context;
 	iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
@@ -185,31 +188,26 @@ iso9660_archive_open_stream(struct archive_file *file, struct input_stream *is,
 		g_free(iis);
 		g_set_error(error_r, iso9660_quark(), 0,
 			    "not found in the ISO file: %s", pathname);
-		return false;
+		return NULL;
 	}
 
-	//setup file ops
-	is->plugin = &iso9660_input_plugin;
-	//insert back reference
-	is->data = iis;
-	is->ready = true;
+	iis->base.ready = true;
 	//we are not seekable
-	is->seekable = false;
+	iis->base.seekable = false;
 
-	is->size = iis->statbuf->size;
+	iis->base.size = iis->statbuf->size;
 
 	iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE);
 
 	refcount_inc(&context->ref);
 
-	return true;
+	return &iis->base;
 }
 
 static void
 iso9660_input_close(struct input_stream *is)
 {
-	struct iso9660_input_stream *iis =
-		(struct iso9660_input_stream *)is->data;
+	struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
 
 	g_free(iis->statbuf);
 
@@ -220,8 +218,7 @@ iso9660_input_close(struct input_stream *is)
 static size_t
 iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r)
 {
-	struct iso9660_input_stream *iis =
-		(struct iso9660_input_stream *)is->data;
+	struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
 	int toread, readed = 0;
 	int no_blocks, cur_block;
 	size_t left_bytes = iis->statbuf->size - is->offset;
diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c
index 3e0c063c2..7e59a0935 100644
--- a/src/archive/zzip_archive_plugin.c
+++ b/src/archive/zzip_archive_plugin.c
@@ -125,13 +125,15 @@ zzip_archive_close(struct archive_file *file)
 /* single archive handling */
 
 struct zzip_input_stream {
+	struct input_stream base;
+
 	struct zzip_archive *archive;
 
 	ZZIP_FILE *file;
 };
 
-static bool
-zzip_archive_open_stream(struct archive_file *file, struct input_stream *is,
+static struct input_stream *
+zzip_archive_open_stream(struct archive_file *file,
 			 const char *pathname, GError **error_r)
 {
 	struct zzip_archive *context = (struct zzip_archive *) file;
@@ -139,6 +141,7 @@ zzip_archive_open_stream(struct archive_file *file, struct input_stream *is,
 	ZZIP_STAT z_stat;
 
 	zis = g_new(struct zzip_input_stream, 1);
+	input_stream_init(&zis->base, &zzip_input_plugin);
 
 	zis->archive = context;
 	zis->file = zzip_file_open(context->dir, pathname, 0);
@@ -146,29 +149,25 @@ zzip_archive_open_stream(struct archive_file *file, struct input_stream *is,
 		g_free(zis);
 		g_set_error(error_r, zzip_quark(), 0,
 			    "not found in the ZIP file: %s", pathname);
-		return false;
+		return NULL;
 	}
 
-	//setup file ops
-	is->plugin = &zzip_input_plugin;
-	//insert back reference
-	is->data = zis;
-	is->ready = true;
+	zis->base.ready = true;
 	//we are seekable (but its not recommendent to do so)
-	is->seekable = true;
+	zis->base.seekable = true;
 
 	zzip_file_stat(zis->file, &z_stat);
-	is->size = z_stat.st_size;
+	zis->base.size = z_stat.st_size;
 
 	refcount_inc(&context->ref);
 
-	return true;
+	return &zis->base;
 }
 
 static void
 zzip_input_close(struct input_stream *is)
 {
-	struct zzip_input_stream *zis = (struct zzip_input_stream *)is->data;
+	struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
 
 	zzip_file_close(zis->file);
 	zzip_archive_close(&zis->archive->base);
@@ -179,7 +178,7 @@ static size_t
 zzip_input_read(struct input_stream *is, void *ptr, size_t size,
 		GError **error_r)
 {
-	struct zzip_input_stream *zis = (struct zzip_input_stream *)is->data;
+	struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
 	int ret;
 
 	ret = zzip_file_read(zis->file, ptr, size);
@@ -197,7 +196,7 @@ zzip_input_read(struct input_stream *is, void *ptr, size_t size,
 static bool
 zzip_input_eof(struct input_stream *is)
 {
-	struct zzip_input_stream *zis = (struct zzip_input_stream *)is->data;
+	struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
 
 	return (goffset)zzip_tell(zis->file) == is->size;
 }
@@ -206,7 +205,7 @@ static bool
 zzip_input_seek(struct input_stream *is,
 		goffset offset, int whence, GError **error_r)
 {
-	struct zzip_input_stream *zis = (struct zzip_input_stream *)is->data;
+	struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
 	zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
 	if (ofs != -1) {
 		g_set_error(error_r, zzip_quark(), ofs,
diff --git a/src/archive_plugin.c b/src/archive_plugin.c
index b43293e75..60da4d283 100644
--- a/src/archive_plugin.c
+++ b/src/archive_plugin.c
@@ -80,13 +80,13 @@ archive_file_scan_next(struct archive_file *file)
 	return file->plugin->scan_next(file);
 }
 
-bool
-archive_file_open_stream(struct archive_file *file, struct input_stream *is,
+struct input_stream *
+archive_file_open_stream(struct archive_file *file,
 			 const char *path, GError **error_r)
 {
 	assert(file != NULL);
 	assert(file->plugin != NULL);
 	assert(file->plugin->open_stream != NULL);
 
-	return file->plugin->open_stream(file, is, path, error_r);
+	return file->plugin->open_stream(file, path, error_r);
 }
diff --git a/src/archive_plugin.h b/src/archive_plugin.h
index 254c81f66..b08c93389 100644
--- a/src/archive_plugin.h
+++ b/src/archive_plugin.h
@@ -71,8 +71,9 @@ struct archive_plugin {
 	 * @param error_r location to store the error occuring, or
 	 * NULL to ignore errors
 	 */
-	bool (*open_stream)(struct archive_file *, struct input_stream *is,
-			    const char *path, GError **error_r);
+	struct input_stream *(*open_stream)(struct archive_file *af,
+					    const char *path,
+					    GError **error_r);
 
 	/**
 	 * closes archive file.
@@ -99,8 +100,8 @@ archive_file_scan_reset(struct archive_file *file);
 char *
 archive_file_scan_next(struct archive_file *file);
 
-bool
-archive_file_open_stream(struct archive_file *file, struct input_stream *is,
+struct input_stream *
+archive_file_open_stream(struct archive_file *file,
 			 const char *path, GError **error_r);
 
 #endif
diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c
index 3eb2b0e68..1615d70d0 100644
--- a/src/decoder/wavpack_plugin.c
+++ b/src/decoder/wavpack_plugin.c
@@ -464,13 +464,12 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder,
 	isp->last_byte = EOF;
 }
 
-static bool
-wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
-		 struct wavpack_input *wpi)
+static struct input_stream *
+wavpack_open_wvc(struct decoder *decoder, struct wavpack_input *wpi)
 {
+	struct input_stream *is_wvc;
 	char *utf8url;
 	char *wvc_url = NULL;
-	bool ret;
 	char first_byte;
 	size_t nbytes;
 
@@ -486,12 +485,11 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
 	wvc_url = g_strconcat(utf8url, "c", NULL);
 	g_free(utf8url);
 
-	ret = input_stream_open(is_wvc, wvc_url, NULL);
+	is_wvc = input_stream_open(wvc_url, NULL);
 	g_free(wvc_url);
 
-	if (!ret) {
-		return false;
-	}
+	if (is_wvc == NULL)
+		return NULL;
 
 	/*
 	 * And we try to buffer in order to get know
@@ -502,13 +500,13 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
 	);
 	if (nbytes == 0) {
 		input_stream_close(is_wvc);
-		return false;
+		return NULL;
 	}
 
 	/* push it back */
 	wavpack_input_init(wpi, decoder, is_wvc);
 	wpi->last_byte = first_byte;
-	return true;
+	return is_wvc;
 }
 
 /*
@@ -519,14 +517,15 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
 {
 	char error[ERRORLEN];
 	WavpackContext *wpc;
-	struct input_stream is_wvc;
+	struct input_stream *is_wvc;
 	int open_flags = OPEN_NORMALIZE;
 	struct wavpack_input isp, isp_wvc;
 	bool can_seek = is->seekable;
 
-	if (wavpack_open_wvc(decoder, &is_wvc, &isp_wvc)) {
+	is_wvc = wavpack_open_wvc(decoder, &isp_wvc);
+	if (is_wvc != NULL) {
 		open_flags |= OPEN_WVC;
-		can_seek &= is_wvc.seekable;
+		can_seek &= is_wvc->seekable;
 	}
 
 	if (!can_seek) {
@@ -549,7 +548,7 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
 
 	WavpackCloseFile(wpc);
 	if (open_flags & OPEN_WVC) {
-		input_stream_close(&is_wvc);
+		input_stream_close(is_wvc);
 	}
 }
 
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index 7e9c820d2..de095216a 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -56,22 +56,23 @@ decoder_lock_get_command(struct decoder_control *dc)
  *
  * Unlock the decoder before calling this function.
  *
- * @return true on success of if #DECODE_COMMAND_STOP is received,
- * false on error
+ * @return an input_stream on success or if #DECODE_COMMAND_STOP is
+ * received, NULL on error
  */
-static bool
-decoder_input_stream_open(struct decoder_control *dc,
-			  struct input_stream *is, const char *uri)
+static struct input_stream *
+decoder_input_stream_open(struct decoder_control *dc, const char *uri)
 {
 	GError *error = NULL;
+	struct input_stream *is;
 
-	if (!input_stream_open(is, uri, &error)) {
+	is = input_stream_open(uri, &error);
+	if (is == NULL) {
 		if (error != NULL) {
 			g_warning("%s", error->message);
 			g_error_free(error);
 		}
 
-		return false;
+		return NULL;
 	}
 
 	/* wait for the input stream to become ready; its metadata
@@ -86,11 +87,11 @@ decoder_input_stream_open(struct decoder_control *dc,
 			input_stream_close(is);
 			g_warning("%s", error->message);
 			g_error_free(error);
-			return false;
+			return NULL;
 		}
 	}
 
-	return true;
+	return is;
 }
 
 static bool
@@ -215,12 +216,13 @@ static bool
 decoder_run_stream(struct decoder *decoder, const char *uri)
 {
 	struct decoder_control *dc = decoder->dc;
-	struct input_stream input_stream;
+	struct input_stream *input_stream;
 	bool success;
 
 	decoder_unlock(dc);
 
-	if (!decoder_input_stream_open(dc, &input_stream, uri)) {
+	input_stream = decoder_input_stream_open(dc, uri);
+	if (input_stream == NULL) {
 		decoder_lock(dc);
 		return false;
 	}
@@ -229,15 +231,15 @@ decoder_run_stream(struct decoder *decoder, const char *uri)
 
 	success = dc->command == DECODE_COMMAND_STOP ||
 		/* first we try mime types: */
-		decoder_run_stream_mime_type(decoder, &input_stream) ||
+		decoder_run_stream_mime_type(decoder, input_stream) ||
 		/* if that fails, try suffix matching the URL: */
-		decoder_run_stream_suffix(decoder, &input_stream, uri) ||
+		decoder_run_stream_suffix(decoder, input_stream, uri) ||
 		/* fallback to mp3: this is needed for bastard streams
 		   that don't have a suffix or set the mimeType */
-		decoder_run_stream_fallback(decoder, &input_stream);
+		decoder_run_stream_fallback(decoder, input_stream);
 
 	decoder_unlock(dc);
-	input_stream_close(&input_stream);
+	input_stream_close(input_stream);
 	decoder_lock(dc);
 
 	return success;
@@ -267,21 +269,21 @@ decoder_run_file(struct decoder *decoder, const char *path_fs)
 
 			decoder_unlock(dc);
 		} else if (plugin->stream_decode != NULL) {
-			struct input_stream input_stream;
+			struct input_stream *input_stream;
 			bool success;
 
-			if (!decoder_input_stream_open(dc, &input_stream,
-						       path_fs))
+			input_stream = decoder_input_stream_open(dc, path_fs);
+			if (input_stream == NULL)
 				continue;
 
 			decoder_lock(dc);
 
 			success = decoder_stream_decode(plugin, decoder,
-							&input_stream);
+							input_stream);
 
 			decoder_unlock(dc);
 
-			input_stream_close(&input_stream);
+			input_stream_close(input_stream);
 
 			if (success) {
 				decoder_lock(dc);
diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c
index 9e3e79692..97e4836ff 100644
--- a/src/input/archive_input_plugin.c
+++ b/src/input/archive_input_plugin.c
@@ -33,24 +33,23 @@
  * parent_stream so tar plugin fetches file data from gzip
  * plugin and gzip fetches file from disk
  */
-static bool
-input_archive_open(struct input_stream *is, const char *pathname,
-		   GError **error_r)
+static struct input_stream *
+input_archive_open(const char *pathname, GError **error_r)
 {
 	const struct archive_plugin *arplug;
 	struct archive_file *file;
 	char *archive, *filename, *suffix, *pname;
-	bool opened;
+	struct input_stream *is;
 
 	if (!g_path_is_absolute(pathname))
-		return false;
+		return NULL;
 
 	pname = g_strdup(pathname);
 	// archive_lookup will modify pname when true is returned
 	if (!archive_lookup(pname, &archive, &filename, &suffix)) {
 		g_debug("not an archive, lookup %s failed\n", pname);
 		g_free(pname);
-		return false;
+		return NULL;
 	}
 
 	//check which archive plugin to use (by ext)
@@ -58,19 +57,19 @@ input_archive_open(struct input_stream *is, const char *pathname,
 	if (!arplug) {
 		g_warning("can't handle archive %s\n",archive);
 		g_free(pname);
-		return false;
+		return NULL;
 	}
 
 	file = archive_file_open(arplug, archive, error_r);
 	if (file == NULL)
-		return false;
+		return NULL;
 
 	//setup fileops
-	opened = archive_file_open_stream(file, is, filename, error_r);
+	is = archive_file_open_stream(file, filename, error_r);
 	archive_file_close(file);
 	g_free(pname);
 
-	return opened;
+	return is;
 }
 
 const struct input_plugin input_plugin_archive = {
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c
index 53f2e0404..c54d994e8 100644
--- a/src/input/curl_input_plugin.c
+++ b/src/input/curl_input_plugin.c
@@ -57,6 +57,8 @@ struct buffer {
 };
 
 struct input_curl {
+	struct input_stream base;
+
 	/* some buffers which were passed to libcurl, which we have
 	   too free */
 	char *url, *range;
@@ -179,10 +181,8 @@ input_curl_easy_free(struct input_curl *c)
  * Frees this stream (but not the input_stream struct itself).
  */
 static void
-input_curl_free(struct input_stream *is)
+input_curl_free(struct input_curl *c)
 {
-	struct input_curl *c = is->data;
-
 	if (c->tag != NULL)
 		tag_free(c->tag);
 	g_free(c->meta_name);
@@ -201,7 +201,7 @@ input_curl_free(struct input_stream *is)
 static struct tag *
 input_curl_tag(struct input_stream *is)
 {
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)is;
 	struct tag *tag = c->tag;
 
 	c->tag = NULL;
@@ -209,9 +209,8 @@ input_curl_tag(struct input_stream *is)
 }
 
 static bool
-input_curl_multi_info_read(struct input_stream *is, GError **error_r)
+input_curl_multi_info_read(struct input_curl *c, GError **error_r)
 {
-	struct input_curl *c = is->data;
 	CURLMsg *msg;
 	int msgs_in_queue;
 
@@ -219,7 +218,7 @@ input_curl_multi_info_read(struct input_stream *is, GError **error_r)
 					   &msgs_in_queue)) != NULL) {
 		if (msg->msg == CURLMSG_DONE) {
 			c->eof = true;
-			is->ready = true;
+			c->base.ready = true;
 
 			if (msg->data.result != CURLE_OK) {
 				g_set_error(error_r, curl_quark(),
@@ -279,7 +278,7 @@ input_curl_select(struct input_curl *c, GError **error_r)
 static bool
 fill_buffer(struct input_stream *is, GError **error_r)
 {
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)is;
 	CURLMcode mcode = CURLM_CALL_MULTI_PERFORM;
 
 	while (!c->eof && g_queue_is_empty(c->buffers)) {
@@ -305,7 +304,7 @@ fill_buffer(struct input_stream *is, GError **error_r)
 			return false;
 		}
 
-		bret = input_curl_multi_info_read(is, error_r);
+		bret = input_curl_multi_info_read(c, error_r);
 		if (!bret)
 			return false;
 	}
@@ -407,7 +406,7 @@ static size_t
 input_curl_read(struct input_stream *is, void *ptr, size_t size,
 		GError **error_r)
 {
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)is;
 	bool success;
 	size_t nbytes = 0;
 	char *dest = ptr;
@@ -441,13 +440,15 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size,
 static void
 input_curl_close(struct input_stream *is)
 {
-	input_curl_free(is);
+	struct input_curl *c = (struct input_curl *)is;
+
+	input_curl_free(c);
 }
 
 static bool
 input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
 {
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)is;
 
 	return c->eof && g_queue_is_empty(c->buffers);
 }
@@ -455,7 +456,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
 static int
 input_curl_buffer(struct input_stream *is, GError **error_r)
 {
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)is;
 	CURLMcode mcode;
 	int running_handles;
 	bool ret;
@@ -483,7 +484,7 @@ input_curl_buffer(struct input_stream *is, GError **error_r)
 		return -1;
 	}
 
-	ret = input_curl_multi_info_read(is, error_r);
+	ret = input_curl_multi_info_read(c, error_r);
 	if (!ret)
 		return -1;
 
@@ -494,8 +495,7 @@ input_curl_buffer(struct input_stream *is, GError **error_r)
 static size_t
 input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
 {
-	struct input_stream *is = stream;
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)stream;
 	const char *header = ptr, *end, *value;
 	char name[64];
 
@@ -524,7 +524,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
 	if (g_ascii_strcasecmp(name, "accept-ranges") == 0) {
 		/* a stream with icy-metadata is not seekable */
 		if (!icy_defined(&c->icy_metadata))
-			is->seekable = true;
+			c->base.seekable = true;
 	} else if (g_ascii_strcasecmp(name, "content-length") == 0) {
 		char buffer[64];
 
@@ -534,10 +534,10 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
 		memcpy(buffer, value, end - value);
 		buffer[end - value] = 0;
 
-		is->size = is->offset + g_ascii_strtoull(buffer, NULL, 10);
+		c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10);
 	} else if (g_ascii_strcasecmp(name, "content-type") == 0) {
-		g_free(is->mime);
-		is->mime = g_strndup(value, end - value);
+		g_free(c->base.mime);
+		c->base.mime = g_strndup(value, end - value);
 	} else if (g_ascii_strcasecmp(name, "icy-name") == 0 ||
 		   g_ascii_strcasecmp(name, "ice-name") == 0 ||
 		   g_ascii_strcasecmp(name, "x-audiocast-name") == 0) {
@@ -568,7 +568,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
 
 			/* a stream with icy-metadata is not
 			   seekable */
-			is->seekable = false;
+			c->base.seekable = false;
 		}
 	}
 
@@ -579,8 +579,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
 static size_t
 input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
 {
-	struct input_stream *is = stream;
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)stream;
 	struct buffer *buffer;
 
 	size *= nmemb;
@@ -594,15 +593,14 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
 	g_queue_push_tail(c->buffers, buffer);
 
 	c->buffered = true;
-	is->ready = true;
+	c->base.ready = true;
 
 	return size;
 }
 
 static bool
-input_curl_easy_init(struct input_stream *is, GError **error_r)
+input_curl_easy_init(struct input_curl *c, GError **error_r)
 {
-	struct input_curl *c = is->data;
 	CURLcode code;
 	CURLMcode mcode;
 
@@ -627,10 +625,10 @@ input_curl_easy_init(struct input_stream *is, GError **error_r)
 			 "Music Player Daemon " VERSION);
 	curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
 			 input_curl_headerfunction);
-	curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is);
+	curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
 	curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
 			 input_curl_writefunction);
-	curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is);
+	curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
 	curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
 	curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
 	curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
@@ -669,9 +667,9 @@ input_curl_easy_init(struct input_stream *is, GError **error_r)
 void
 input_curl_reinit(struct input_stream *is)
 {
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)is;
 
-	assert(is->plugin == &input_plugin_curl);
+	assert(c->base.plugin == &input_plugin_curl);
 	assert(c->easy != NULL);
 
 	curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is);
@@ -702,7 +700,7 @@ static bool
 input_curl_seek(struct input_stream *is, goffset offset, int whence,
 		GError **error_r)
 {
-	struct input_curl *c = is->data;
+	struct input_curl *c = (struct input_curl *)is;
 	bool ret;
 
 	assert(is->ready);
@@ -774,7 +772,7 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence,
 		return true;
 	}
 
-	ret = input_curl_easy_init(is, error_r);
+	ret = input_curl_easy_init(c, error_r);
 	if (!ret)
 		return false;
 
@@ -789,55 +787,54 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence,
 	if (!ret)
 		return false;
 
-	return input_curl_multi_info_read(is, error_r);
+	return input_curl_multi_info_read(c, error_r);
 }
 
-static bool
-input_curl_open(struct input_stream *is, const char *url, GError **error_r)
+static struct input_stream *
+input_curl_open(const char *url, GError **error_r)
 {
 	struct input_curl *c;
 	bool ret;
 
 	if (strncmp(url, "http://", 7) != 0)
-		return false;
+		return NULL;
 
 	c = g_new0(struct input_curl, 1);
+	input_stream_init(&c->base, &input_plugin_curl);
+
 	c->url = g_strdup(url);
 	c->buffers = g_queue_new();
 
-	is->plugin = &input_plugin_curl;
-	is->data = c;
-
 	c->multi = curl_multi_init();
 	if (c->multi == NULL) {
 		g_set_error(error_r, curl_quark(), 0,
 			    "curl_multi_init() failed");
-		input_curl_free(is);
-		return false;
+		input_curl_free(c);
+		return NULL;
 	}
 
 	icy_clear(&c->icy_metadata);
 	c->tag = NULL;
 
-	ret = input_curl_easy_init(is, error_r);
+	ret = input_curl_easy_init(c, error_r);
 	if (!ret) {
-		input_curl_free(is);
-		return false;
+		input_curl_free(c);
+		return NULL;
 	}
 
 	ret = input_curl_send_request(c, error_r);
 	if (!ret) {
-		input_curl_free(is);
-		return false;
+		input_curl_free(c);
+		return NULL;
 	}
 
-	ret = input_curl_multi_info_read(is, error_r);
+	ret = input_curl_multi_info_read(c, error_r);
 	if (!ret) {
-		input_curl_free(is);
-		return false;
+		input_curl_free(c);
+		return NULL;
 	}
 
-	return true;
+	return &c->base;
 }
 
 const struct input_plugin input_plugin_curl = {
diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c
index 92da42290..1c5813f88 100644
--- a/src/input/file_input_plugin.c
+++ b/src/input/file_input_plugin.c
@@ -32,18 +32,24 @@
 #undef G_LOG_DOMAIN
 #define G_LOG_DOMAIN "input_file"
 
+struct file_input_stream {
+	struct input_stream base;
+
+	int fd;
+};
+
 static inline GQuark
 file_quark(void)
 {
 	return g_quark_from_static_string("file");
 }
 
-static bool
-input_file_open(struct input_stream *is, const char *filename,
-		GError **error_r)
+static struct input_stream *
+input_file_open(const char *filename, GError **error_r)
 {
 	int fd, ret;
 	struct stat st;
+	struct file_input_stream *fis;
 
 	if (!g_path_is_absolute(filename))
 		return false;
@@ -57,8 +63,6 @@ input_file_open(struct input_stream *is, const char *filename,
 		return false;
 	}
 
-	is->seekable = true;
-
 	ret = fstat(fd, &st);
 	if (ret < 0) {
 		g_set_error(error_r, file_quark(), errno,
@@ -75,26 +79,29 @@ input_file_open(struct input_stream *is, const char *filename,
 		return false;
 	}
 
-	is->size = st.st_size;
-
 #ifdef POSIX_FADV_SEQUENTIAL
-	posix_fadvise(fd, (off_t)0, is->size, POSIX_FADV_SEQUENTIAL);
+	posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
 #endif
 
-	is->plugin = &input_plugin_file;
-	is->data = GINT_TO_POINTER(fd);
-	is->ready = true;
+	fis = g_new(struct file_input_stream, 1);
+	input_stream_init(&fis->base, &input_plugin_file);
 
-	return true;
+	fis->base.size = st.st_size;
+	fis->base.seekable = true;
+	fis->base.ready = true;
+
+	fis->fd = fd;
+
+	return &fis->base;
 }
 
 static bool
 input_file_seek(struct input_stream *is, goffset offset, int whence,
 		GError **error_r)
 {
-	int fd = GPOINTER_TO_INT(is->data);
+	struct file_input_stream *fis = (struct file_input_stream *)is;
 
-	offset = (goffset)lseek(fd, (off_t)offset, whence);
+	offset = (goffset)lseek(fis->fd, (off_t)offset, whence);
 	if (offset < 0) {
 		g_set_error(error_r, file_quark(), errno,
 			    "Failed to seek: %s", g_strerror(errno));
@@ -109,10 +116,10 @@ static size_t
 input_file_read(struct input_stream *is, void *ptr, size_t size,
 		GError **error_r)
 {
-	int fd = GPOINTER_TO_INT(is->data);
+	struct file_input_stream *fis = (struct file_input_stream *)is;
 	ssize_t nbytes;
 
-	nbytes = read(fd, ptr, size);
+	nbytes = read(fis->fd, ptr, size);
 	if (nbytes < 0) {
 		g_set_error(error_r, file_quark(), errno,
 			    "Failed to read: %s", g_strerror(errno));
@@ -126,9 +133,10 @@ input_file_read(struct input_stream *is, void *ptr, size_t size,
 static void
 input_file_close(struct input_stream *is)
 {
-	int fd = GPOINTER_TO_INT(is->data);
+	struct file_input_stream *fis = (struct file_input_stream *)is;
 
-	close(fd);
+	close(fis->fd);
+	g_free(fis);
 }
 
 static bool
diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c
index e7dc56bc8..5055eb05e 100644
--- a/src/input/mms_input_plugin.c
+++ b/src/input/mms_input_plugin.c
@@ -31,6 +31,8 @@
 #define G_LOG_DOMAIN "input_mms"
 
 struct input_mms {
+	struct input_stream base;
+
 	mmsx_t *mms;
 
 	bool eof;
@@ -42,8 +44,8 @@ mms_quark(void)
 	return g_quark_from_static_string("mms");
 }
 
-static bool
-input_mms_open(struct input_stream *is, const char *url, GError **error_r)
+static struct input_stream *
+input_mms_open(const char *url, GError **error_r)
 {
 	struct input_mms *m;
 
@@ -51,30 +53,31 @@ input_mms_open(struct input_stream *is, const char *url, GError **error_r)
 	    !g_str_has_prefix(url, "mmsh://") &&
 	    !g_str_has_prefix(url, "mmst://") &&
 	    !g_str_has_prefix(url, "mmsu://"))
-		return false;
+		return NULL;
 
 	m = g_new(struct input_mms, 1);
+	input_stream_init(&m->base, &input_plugin_mms);
+
 	m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
 	if (m->mms == NULL) {
 		g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed");
-		return false;
+		return NULL;
 	}
 
 	/* XX is this correct?  at least this selects the ffmpeg
 	   decoder, which seems to work fine*/
-	is->mime = g_strdup("audio/x-ms-wma");
+	m->base.mime = g_strdup("audio/x-ms-wma");
+
+	m->base.ready = true;
 
-	is->plugin = &input_plugin_mms;
-	is->data = m;
-	is->ready = true;
-	return true;
+	return &m->base;
 }
 
 static size_t
 input_mms_read(struct input_stream *is, void *ptr, size_t size,
 	       GError **error_r)
 {
-	struct input_mms *m = is->data;
+	struct input_mms *m = (struct input_mms *)is;
 	int ret;
 
 	ret = mmsx_read(NULL, m->mms, ptr, size);
@@ -97,7 +100,7 @@ input_mms_read(struct input_stream *is, void *ptr, size_t size,
 static void
 input_mms_close(struct input_stream *is)
 {
-	struct input_mms *m = is->data;
+	struct input_mms *m = (struct input_mms *)is;
 
 	mmsx_close(m->mms);
 	g_free(m);
@@ -106,7 +109,7 @@ input_mms_close(struct input_stream *is)
 static bool
 input_mms_eof(struct input_stream *is)
 {
-	struct input_mms *m = is->data;
+	struct input_mms *m = (struct input_mms *)is;
 
 	return m->eof;
 }
diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c
index 54efa75fb..68b3e8036 100644
--- a/src/input/rewind_input_plugin.c
+++ b/src/input/rewind_input_plugin.c
@@ -32,7 +32,9 @@
 #define G_LOG_DOMAIN "input_rewind"
 
 struct input_rewind {
-	struct input_stream input;
+	struct input_stream base;
+
+	struct input_stream *input;
 
 	/**
 	 * The read position within the buffer.  Undefined as long as
@@ -61,11 +63,9 @@ struct input_rewind {
  * contain more data for the next read operation?
  */
 static bool
-reading_from_buffer(const struct input_stream *is)
+reading_from_buffer(const struct input_rewind *r)
 {
-	const struct input_rewind *r = is->data;
-
-	return r->tail > 0 && is->offset < r->input.offset;
+	return r->tail > 0 && r->base.offset < r->input->offset;
 }
 
 /**
@@ -75,10 +75,10 @@ reading_from_buffer(const struct input_stream *is)
  * attributes.
  */
 static void
-copy_attributes(struct input_stream *dest)
+copy_attributes(struct input_rewind *r)
 {
-	const struct input_rewind *r = dest->data;
-	const struct input_stream *src = &r->input;
+	struct input_stream *dest = &r->base;
+	const struct input_stream *src = r->input;
 
 	dest->ready = src->ready;
 	dest->seekable = src->seekable;
@@ -94,9 +94,9 @@ copy_attributes(struct input_stream *dest)
 static void
 input_rewind_close(struct input_stream *is)
 {
-	struct input_rewind *r = is->data;
+	struct input_rewind *r = (struct input_rewind *)is;
 
-	input_stream_close(&r->input);
+	input_stream_close(r->input);
 
 	g_free(r);
 }
@@ -104,19 +104,19 @@ input_rewind_close(struct input_stream *is)
 static struct tag *
 input_rewind_tag(struct input_stream *is)
 {
-	struct input_rewind *r = is->data;
+	struct input_rewind *r = (struct input_rewind *)is;
 
-	return input_stream_tag(&r->input);
+	return input_stream_tag(r->input);
 }
 
 static int
 input_rewind_buffer(struct input_stream *is, GError **error_r)
 {
-	struct input_rewind *r = is->data;
+	struct input_rewind *r = (struct input_rewind *)is;
 
-	int ret = input_stream_buffer(&r->input, error_r);
-	if (ret < 0 || !reading_from_buffer(is))
-		copy_attributes(is);
+	int ret = input_stream_buffer(r->input, error_r);
+	if (ret < 0 || !reading_from_buffer(r))
+		copy_attributes(r);
 
 	return ret;
 }
@@ -125,13 +125,13 @@ static size_t
 input_rewind_read(struct input_stream *is, void *ptr, size_t size,
 		  GError **error_r)
 {
-	struct input_rewind *r = is->data;
+	struct input_rewind *r = (struct input_rewind *)is;
 
-	if (reading_from_buffer(is)) {
+	if (reading_from_buffer(r)) {
 		/* buffered read */
 
 		assert(r->head == (size_t)is->offset);
-		assert(r->tail == (size_t)r->input.offset);
+		assert(r->tail == (size_t)r->input->offset);
 
 		if (size > r->tail - r->head)
 			size = r->tail - r->head;
@@ -144,9 +144,9 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size,
 	} else {
 		/* pass method call to underlying stream */
 
-		size_t nbytes = input_stream_read(&r->input, ptr, size, error_r);
+		size_t nbytes = input_stream_read(r->input, ptr, size, error_r);
 
-		if (r->input.offset > (goffset)sizeof(r->buffer))
+		if (r->input->offset > (goffset)sizeof(r->buffer))
 			/* disable buffering */
 			r->tail = 0;
 		else if (r->tail == (size_t)is->offset) {
@@ -155,46 +155,46 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size,
 			memcpy(r->buffer + r->tail, ptr, nbytes);
 			r->tail += nbytes;
 
-			assert(r->tail == (size_t)r->input.offset);
+			assert(r->tail == (size_t)r->input->offset);
 		}
 
-		copy_attributes(is);
+		copy_attributes(r);
 
 		return nbytes;
 	}
 }
 
 static bool
-input_rewind_eof(G_GNUC_UNUSED struct input_stream *is)
+input_rewind_eof(struct input_stream *is)
 {
-	struct input_rewind *r = is->data;
+	struct input_rewind *r = (struct input_rewind *)is;
 
-	return !reading_from_buffer(is) && input_stream_eof(&r->input);
+	return !reading_from_buffer(r) && input_stream_eof(r->input);
 }
 
 static bool
 input_rewind_seek(struct input_stream *is, goffset offset, int whence,
 		  GError **error_r)
 {
-	struct input_rewind *r = is->data;
+	struct input_rewind *r = (struct input_rewind *)is;
 
 	assert(is->ready);
 
 	if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) {
 		/* buffered seek */
 
-		assert(!reading_from_buffer(is) ||
+		assert(!reading_from_buffer(r) ||
 		       r->head == (size_t)is->offset);
-		assert(r->tail == (size_t)r->input.offset);
+		assert(r->tail == (size_t)r->input->offset);
 
 		r->head = (size_t)offset;
 		is->offset = offset;
 
 		return true;
 	} else {
-		bool success = input_stream_seek(&r->input, offset, whence,
+		bool success = input_stream_seek(r->input, offset, whence,
 						 error_r);
-		copy_attributes(is);
+		copy_attributes(r);
 
 		/* disable the buffer, because r->input has left the
 		   buffered range now */
@@ -213,7 +213,7 @@ static const struct input_plugin rewind_input_plugin = {
 	.seek = input_rewind_seek,
 };
 
-void
+struct input_stream *
 input_rewind_open(struct input_stream *is)
 {
 	struct input_rewind *c;
@@ -224,17 +224,12 @@ input_rewind_open(struct input_stream *is)
 	if (is->plugin != &input_plugin_curl)
 		/* due to limitations in the input_plugin API, we only
 		   (explicitly) support the CURL input plugin */
-		return;
+		return is;
 
 	c = g_new(struct input_rewind, 1);
+	input_stream_init(&c->base, &rewind_input_plugin);
 	c->tail = 0;
+	c->input = is;
 
-	/* move the CURL input stream to c->input */
-	c->input = *is;
-	input_curl_reinit(&c->input);
-
-	/* convert the existing input_stream pointer to a "rewind"
-	   input stream */
-	is->plugin = &rewind_input_plugin;
-	is->data = c;
+	return &c->base;
 }
diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h
index 83152e588..27ad169b6 100644
--- a/src/input/rewind_input_plugin.h
+++ b/src/input/rewind_input_plugin.h
@@ -33,15 +33,15 @@ struct input_stream;
 
 #ifdef ENABLE_CURL
 
-void
+struct input_stream *
 input_rewind_open(struct input_stream *is);
 
 #else
 
-static inline void
+static inline struct input_stream *
 input_rewind_open(struct input_stream *is)
 {
-	(void)is;
+	return is;
 }
 
 #endif
diff --git a/src/input_plugin.h b/src/input_plugin.h
index bc48b9d4b..10be48dbb 100644
--- a/src/input_plugin.h
+++ b/src/input_plugin.h
@@ -48,8 +48,7 @@ struct input_plugin {
 	 */
 	void (*finish)(void);
 
-	bool (*open)(struct input_stream *is, const char *url,
-		     GError **error_r);
+	struct input_stream *(*open)(const char *uri, GError **error_r);
 	void (*close)(struct input_stream *is);
 
 	struct tag *(*tag)(struct input_stream *is);
diff --git a/src/input_stream.c b/src/input_stream.c
index 0cf0558d6..96c692176 100644
--- a/src/input_stream.c
+++ b/src/input_stream.c
@@ -32,38 +32,34 @@ input_quark(void)
 	return g_quark_from_static_string("input");
 }
 
-bool
-input_stream_open(struct input_stream *is, const char *url, GError **error_r)
+struct input_stream *
+input_stream_open(const char *url, GError **error_r)
 {
 	GError *error = NULL;
 
 	assert(error_r == NULL || *error_r == NULL);
 
-	is->seekable = false;
-	is->ready = false;
-	is->offset = 0;
-	is->size = -1;
-	is->mime = NULL;
-
 	for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
 		const struct input_plugin *plugin = input_plugins[i];
+		struct input_stream *is;
 
 		if (!input_plugins_enabled[i])
 			continue;
 
-		if (plugin->open(is, url, &error)) {
+		is = plugin->open(url, &error);
+		if (is != NULL) {
 			assert(is->plugin != NULL);
 			assert(is->plugin->close != NULL);
 			assert(is->plugin->read != NULL);
 			assert(is->plugin->eof != NULL);
 			assert(!is->seekable || is->plugin->seek != NULL);
 
-			input_rewind_open(is);
+			is = input_rewind_open(is);
 
-			return true;
+			return is;
 		} else if (error != NULL) {
 			g_propagate_error(error_r, error);
-			return false;
+			return NULL;
 		}
 	}
 
@@ -103,9 +99,9 @@ input_stream_read(struct input_stream *is, void *ptr, size_t size,
 
 void input_stream_close(struct input_stream *is)
 {
-	is->plugin->close(is);
-
 	g_free(is->mime);
+
+	is->plugin->close(is);
 }
 
 bool input_stream_eof(struct input_stream *is)
diff --git a/src/input_stream.h b/src/input_stream.h
index 069a9f581..e48f7eb26 100644
--- a/src/input_stream.h
+++ b/src/input_stream.h
@@ -38,11 +38,6 @@ struct input_stream {
 	 */
 	const struct input_plugin *plugin;
 
-	/**
-	 * an opaque pointer managed by the plugin
-	 */
-	void *data;
-
 	/**
 	 * indicates whether the stream is ready for reading and
 	 * whether the other attributes in this struct are valid
@@ -70,20 +65,28 @@ struct input_stream {
 	char *mime;
 };
 
+static inline void
+input_stream_init(struct input_stream *is, const struct input_plugin *plugin)
+{
+	is->plugin = plugin;
+	is->ready = false;
+	is->seekable = false;
+	is->size = -1;
+	is->offset = 0;
+	is->mime = NULL;
+}
+
 /**
  * Opens a new input stream.  You may not access it until the "ready"
  * flag is set.
  *
- * @param is the input_stream object allocated by the caller
- * @return true on success
+ * @return an #input_stream object on success, NULL on error
  */
-bool
-input_stream_open(struct input_stream *is, const char *url, GError **error_r);
+struct input_stream *
+input_stream_open(const char *uri, GError **error_r);
 
 /**
- * Closes the input stream and free resources.  This does not free the
- * input_stream pointer itself, because it is assumed to be allocated
- * by the caller.
+ * Close the input stream and free resources.
  */
 void
 input_stream_close(struct input_stream *is);
diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c
index 25e1adb1d..afb3979d9 100644
--- a/src/playlist/lastfm_playlist_plugin.c
+++ b/src/playlist/lastfm_playlist_plugin.c
@@ -35,7 +35,7 @@
 struct lastfm_playlist {
 	struct playlist_provider base;
 
-	struct input_stream is;
+	struct input_stream *is;
 
 	struct playlist_provider *xspf;
 };
@@ -85,15 +85,14 @@ lastfm_finish(void)
 static char *
 lastfm_get(const char *url)
 {
-	struct input_stream input_stream;
+	struct input_stream *input_stream;
 	GError *error = NULL;
-	bool success;
 	int ret;
 	char buffer[4096];
 	size_t length = 0, nbytes;
 
-	success = input_stream_open(&input_stream, url, &error);
-	if (!success) {
+	input_stream = input_stream_open(url, &error);
+	if (input_stream == NULL) {
 		if (error != NULL) {
 			g_warning("%s", error->message);
 			g_error_free(error);
@@ -102,10 +101,10 @@ lastfm_get(const char *url)
 		return NULL;
 	}
 
-	while (!input_stream.ready) {
-		ret = input_stream_buffer(&input_stream, &error);
+	while (!input_stream->ready) {
+		ret = input_stream_buffer(input_stream, &error);
 		if (ret < 0) {
-			input_stream_close(&input_stream);
+			input_stream_close(input_stream);
 			g_warning("%s", error->message);
 			g_error_free(error);
 			return NULL;
@@ -113,7 +112,7 @@ lastfm_get(const char *url)
 	}
 
 	do {
-		nbytes = input_stream_read(&input_stream, buffer + length,
+		nbytes = input_stream_read(input_stream, buffer + length,
 					   sizeof(buffer) - length, &error);
 		if (nbytes == 0) {
 			if (error != NULL) {
@@ -121,18 +120,18 @@ lastfm_get(const char *url)
 				g_error_free(error);
 			}
 
-			if (input_stream_eof(&input_stream))
+			if (input_stream_eof(input_stream))
 				break;
 
 			/* I/O error */
-			input_stream_close(&input_stream);
+			input_stream_close(input_stream);
 			return NULL;
 		}
 
 		length += nbytes;
 	} while (length < sizeof(buffer));
 
-	input_stream_close(&input_stream);
+	input_stream_close(input_stream);
 	return g_strndup(buffer, length);
 }
 
@@ -168,7 +167,6 @@ lastfm_open_uri(const char *uri)
 	struct lastfm_playlist *playlist;
 	GError *error = NULL;
 	char *p, *q, *response, *session;
-	bool success;
 
 	/* handshake */
 
@@ -231,10 +229,10 @@ lastfm_open_uri(const char *uri)
 			NULL);
 	g_free(session);
 
-	success = input_stream_open(&playlist->is, p, &error);
+	playlist->is = input_stream_open(p, &error);
 	g_free(p);
 
-	if (!success) {
+	if (playlist->is == NULL) {
 		if (error != NULL) {
 			g_warning("Failed to load XSPF playlist: %s",
 				  error->message);
@@ -245,10 +243,10 @@ lastfm_open_uri(const char *uri)
 		return NULL;
 	}
 
-	while (!playlist->is.ready) {
-		int ret = input_stream_buffer(&playlist->is, &error);
+	while (!playlist->is->ready) {
+		int ret = input_stream_buffer(playlist->is, &error);
 		if (ret < 0) {
-			input_stream_close(&playlist->is);
+			input_stream_close(playlist->is);
 			g_free(playlist);
 			g_warning("%s", error->message);
 			g_error_free(error);
@@ -262,14 +260,14 @@ lastfm_open_uri(const char *uri)
 
 	/* last.fm does not send a MIME type, we have to fake it here
 	   :-( */
-	g_free(playlist->is.mime);
-	playlist->is.mime = g_strdup("application/xspf+xml");
+	g_free(playlist->is->mime);
+	playlist->is->mime = g_strdup("application/xspf+xml");
 
 	/* parse the XSPF playlist */
 
-	playlist->xspf = playlist_list_open_stream(&playlist->is, NULL);
+	playlist->xspf = playlist_list_open_stream(playlist->is, NULL);
 	if (playlist->xspf == NULL) {
-		input_stream_close(&playlist->is);
+		input_stream_close(playlist->is);
 		g_free(playlist);
 		g_warning("Failed to parse XSPF playlist");
 		return NULL;
@@ -284,7 +282,7 @@ lastfm_close(struct playlist_provider *_playlist)
 	struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
 
 	playlist_plugin_close(playlist->xspf);
-	input_stream_close(&playlist->is);
+	input_stream_close(playlist->is);
 	g_free(playlist);
 }
 
diff --git a/src/playlist_list.c b/src/playlist_list.c
index 46fd238c1..d034f4c1e 100644
--- a/src/playlist_list.c
+++ b/src/playlist_list.c
@@ -293,10 +293,11 @@ playlist_suffix_supported(const char *suffix)
 }
 
 struct playlist_provider *
-playlist_list_open_path(struct input_stream *is, const char *path_fs)
+playlist_list_open_path(const char *path_fs)
 {
 	GError *error = NULL;
 	const char *suffix;
+	struct input_stream *is;
 	struct playlist_provider *playlist;
 
 	assert(path_fs != NULL);
@@ -305,7 +306,8 @@ playlist_list_open_path(struct input_stream *is, const char *path_fs)
 	if (suffix == NULL || !playlist_suffix_supported(suffix))
 		return NULL;
 
-	if (!input_stream_open(is, path_fs, &error)) {
+	is = input_stream_open(path_fs, &error);
+	if (is == NULL) {
 		if (error != NULL) {
 			g_warning("%s", error->message);
 			g_error_free(error);
diff --git a/src/playlist_list.h b/src/playlist_list.h
index 84e179b4d..f19fa6579 100644
--- a/src/playlist_list.h
+++ b/src/playlist_list.h
@@ -54,12 +54,10 @@ playlist_list_open_stream(struct input_stream *is, const char *uri);
 /**
  * Opens a playlist from a local file.
  *
- * @param is an uninitialized #input_stream object (must be closed
- * with input_stream_close() if this function succeeds)
  * @param path_fs the path of the playlist file
  * @return a playlist, or NULL on error
  */
 struct playlist_provider *
-playlist_list_open_path(struct input_stream *is, const char *path_fs);
+playlist_list_open_path(const char *path_fs);
 
 #endif
diff --git a/src/playlist_queue.c b/src/playlist_queue.c
index 5c000b148..327cdcacc 100644
--- a/src/playlist_queue.c
+++ b/src/playlist_queue.c
@@ -163,16 +163,15 @@ playlist_open_remote_into_queue(const char *uri, struct playlist *dest)
 {
 	GError *error = NULL;
 	struct playlist_provider *playlist;
-	bool stream = false;
-	struct input_stream is;
+	struct input_stream *is = NULL;
 	enum playlist_result result;
 
 	assert(uri_has_scheme(uri));
 
 	playlist = playlist_list_open_uri(uri);
 	if (playlist == NULL) {
-		stream = input_stream_open(&is, uri, &error);
-		if (!stream) {
+		is = input_stream_open(uri, &error);
+		if (is == NULL) {
 			if (error != NULL) {
 				g_warning("Failed to open %s: %s",
 					  uri, error->message);
@@ -182,9 +181,9 @@ playlist_open_remote_into_queue(const char *uri, struct playlist *dest)
 			return PLAYLIST_RESULT_NO_SUCH_LIST;
 		}
 
-		playlist = playlist_list_open_stream(&is, uri);
+		playlist = playlist_list_open_stream(is, uri);
 		if (playlist == NULL) {
-			input_stream_close(&is);
+			input_stream_close(is);
 			return PLAYLIST_RESULT_NO_SUCH_LIST;
 		}
 	}
@@ -192,8 +191,8 @@ playlist_open_remote_into_queue(const char *uri, struct playlist *dest)
 	result = playlist_load_into_queue(uri, playlist, dest);
 	playlist_plugin_close(playlist);
 
-	if (stream)
-		input_stream_close(&is);
+	if (is != NULL)
+		input_stream_close(is);
 
 	return result;
 }
@@ -203,15 +202,13 @@ playlist_open_path_into_queue(const char *path_fs, const char *uri,
 			      struct playlist *dest)
 {
 	struct playlist_provider *playlist;
-	struct input_stream is;
 	enum playlist_result result;
 
 	if ((playlist = playlist_list_open_uri(path_fs)) != NULL)
 		result = playlist_load_into_queue(uri, playlist, dest);
-	else if ((playlist = playlist_list_open_path(&is, path_fs)) != NULL) {
+	else if ((playlist = playlist_list_open_path(path_fs)) != NULL)
 		result = playlist_load_into_queue(uri, playlist, dest);
-		input_stream_close(&is);
-	} else
+	else
 		return PLAYLIST_RESULT_NO_SUCH_LIST;
 
 	playlist_plugin_close(playlist);
diff --git a/src/song_update.c b/src/song_update.c
index a264c75da..b418b600e 100644
--- a/src/song_update.c
+++ b/src/song_update.c
@@ -101,7 +101,7 @@ song_file_update(struct song *song)
 	char *path_fs;
 	const struct decoder_plugin *plugin;
 	struct stat st;
-	struct input_stream is = { .plugin = NULL };
+	struct input_stream *is = NULL;
 
 	assert(song_is_file(song));
 
@@ -141,26 +141,25 @@ song_file_update(struct song *song)
 		if (plugin->stream_tag != NULL) {
 			/* open the input_stream (if not already
 			   open) */
-			if (is.plugin == NULL &&
-			    !input_stream_open(&is, path_fs, NULL))
-				is.plugin = NULL;
+			if (is == NULL)
+				is = input_stream_open(path_fs, NULL);
 
 			/* now try the stream_tag() method */
-			if (is.plugin != NULL) {
+			if (is != NULL) {
 				song->tag = decoder_plugin_stream_tag(plugin,
-								      &is);
+								      is);
 				if (song->tag != NULL)
 					break;
 
-				input_stream_seek(&is, 0, SEEK_SET, NULL);
+				input_stream_seek(is, 0, SEEK_SET, NULL);
 			}
 		}
 
 		plugin = decoder_plugin_from_suffix(suffix, plugin);
 	} while (plugin != NULL);
 
-	if (is.plugin != NULL)
-		input_stream_close(&is);
+	if (is != NULL)
+		input_stream_close(is);
 
 	if (song->tag != NULL && tag_is_empty(song->tag))
 		song->tag = tag_fallback(path_fs, song->tag);
diff --git a/test/dump_playlist.c b/test/dump_playlist.c
index 7be9b973f..95207e24d 100644
--- a/test/dump_playlist.c
+++ b/test/dump_playlist.c
@@ -44,8 +44,7 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
 int main(int argc, char **argv)
 {
 	const char *uri;
-	struct input_stream is;
-	bool stream_open = false;
+	struct input_stream *is = NULL;
 	bool success;
 	GError *error = NULL;
 	struct playlist_provider *playlist;
@@ -88,8 +87,8 @@ int main(int argc, char **argv)
 	if (playlist == NULL) {
 		/* open the stream and wait until it becomes ready */
 
-		success = input_stream_open(&is, uri, &error);
-		if (!success) {
+		is = input_stream_open(uri, &error);
+		if (is == NULL) {
 			if (error != NULL) {
 				g_warning("%s", error->message);
 				g_error_free(error);
@@ -98,8 +97,8 @@ int main(int argc, char **argv)
 			return 2;
 		}
 
-		while (!is.ready) {
-			int ret = input_stream_buffer(&is, &error);
+		while (!is->ready) {
+			int ret = input_stream_buffer(is, &error);
 			if (ret < 0) {
 				/* error */
 				g_warning("%s", error->message);
@@ -112,13 +111,11 @@ int main(int argc, char **argv)
 				g_usleep(10000);
 		}
 
-		stream_open = true;
-
 		/* open the playlist */
 
-		playlist = playlist_list_open_stream(&is, uri);
+		playlist = playlist_list_open_stream(is, uri);
 		if (playlist == NULL) {
-			input_stream_close(&is);
+			input_stream_close(is);
 			g_printerr("Failed to open playlist\n");
 			return 2;
 		}
@@ -145,8 +142,8 @@ int main(int argc, char **argv)
 	/* deinitialize everything */
 
 	playlist_plugin_close(playlist);
-	if (stream_open)
-		input_stream_close(&is);
+	if (is != NULL)
+		input_stream_close(is);
 	playlist_list_global_finish();
 	input_stream_global_finish();
 	config_global_finish();
diff --git a/test/read_tags.c b/test/read_tags.c
index a16b703f0..8138c187f 100644
--- a/test/read_tags.c
+++ b/test/read_tags.c
@@ -170,17 +170,17 @@ int main(int argc, char **argv)
 
 	tag = decoder_plugin_tag_dup(plugin, path);
 	if (tag == NULL && plugin->stream_tag != NULL) {
-		struct input_stream is;
+		struct input_stream *is = input_stream_open(path, &error);
 
-		if (!input_stream_open(&is, path, &error)) {
+		if (is == NULL) {
 			g_printerr("Failed to open %s: %s\n",
 				   path, error->message);
 			g_error_free(error);
 			return 1;
 		}
 
-		tag = decoder_plugin_stream_tag(plugin, &is);
-		input_stream_close(&is);
+		tag = decoder_plugin_stream_tag(plugin, is);
+		input_stream_close(is);
 	}
 
 	decoder_plugin_deinit_all();
diff --git a/test/run_decoder.c b/test/run_decoder.c
index 9ec08af4d..5a41ef8f6 100644
--- a/test/run_decoder.c
+++ b/test/run_decoder.c
@@ -145,7 +145,6 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
 int main(int argc, char **argv)
 {
 	GError *error = NULL;
-	bool ret;
 	const char *decoder_name;
 	struct decoder decoder;
 
@@ -179,10 +178,9 @@ int main(int argc, char **argv)
 		decoder_plugin_file_decode(decoder.plugin, &decoder,
 					   decoder.uri);
 	} else if (decoder.plugin->stream_decode != NULL) {
-		struct input_stream is;
-
-		ret = input_stream_open(&is, decoder.uri, &error);
-		if (!ret) {
+		struct input_stream *is =
+			input_stream_open(decoder.uri, &error);
+		if (is == NULL) {
 			if (error != NULL) {
 				g_warning("%s", error->message);
 				g_error_free(error);
@@ -192,9 +190,9 @@ int main(int argc, char **argv)
 			return 1;
 		}
 
-		decoder_plugin_stream_decode(decoder.plugin, &decoder, &is);
+		decoder_plugin_stream_decode(decoder.plugin, &decoder, is);
 
-		input_stream_close(&is);
+		input_stream_close(is);
 	} else {
 		g_printerr("Decoder plugin is not usable\n");
 		return 1;
diff --git a/test/run_input.c b/test/run_input.c
index 581b22dca..9a31019dd 100644
--- a/test/run_input.c
+++ b/test/run_input.c
@@ -103,7 +103,7 @@ dump_input_stream(struct input_stream *is)
 int main(int argc, char **argv)
 {
 	GError *error = NULL;
-	struct input_stream is;
+	struct input_stream *is;
 	int ret;
 
 	if (argc != 2) {
@@ -133,9 +133,10 @@ int main(int argc, char **argv)
 
 	/* open the stream and dump it */
 
-	if (input_stream_open(&is, argv[1], &error)) {
-		ret = dump_input_stream(&is);
-		input_stream_close(&is);
+	is = input_stream_open(argv[1], &error);
+	if (is != NULL) {
+		ret = dump_input_stream(is);
+		input_stream_close(is);
 	} else {
 		if (error != NULL) {
 			g_warning("%s", error->message);
-- 
cgit v1.2.3