diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx
index 7bdbb211a..3de948759 100644
--- a/src/input/plugins/FfmpegInputPlugin.cxx
+++ b/src/input/plugins/FfmpegInputPlugin.cxx
@@ -21,28 +21,25 @@
 #define __STDC_CONSTANT_MACROS
 
 #include "FfmpegInputPlugin.hxx"
+#include "lib/ffmpeg/IOContext.hxx"
 #include "lib/ffmpeg/Init.hxx"
 #include "lib/ffmpeg/Error.hxx"
 #include "../InputStream.hxx"
 #include "../InputPlugin.hxx"
 #include "PluginUnavailable.hxx"
 
-extern "C" {
-#include <libavformat/avio.h>
-}
-
 class FfmpegInputStream final : public InputStream {
-	AVIOContext *h;
+	Ffmpeg::IOContext io;
 
 	bool eof = false;
 
 public:
-	FfmpegInputStream(const char *_uri, Mutex &_mutex,
-			  AVIOContext *_h)
+	FfmpegInputStream(const char *_uri, Mutex &_mutex)
 		:InputStream(_uri, _mutex),
-		 h(_h) {
-		seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
-		size = avio_size(h);
+		 io(_uri, AVIO_FLAG_READ)
+	{
+		seekable = (io->seekable & AVIO_SEEKABLE_NORMAL) != 0;
+		size = io.GetSize();
 
 		/* hack to make MPD select the "ffmpeg" decoder plugin
 		   - since avio.h doesn't tell us the MIME type of the
@@ -52,10 +49,6 @@ public:
 		SetReady();
 	}
 
-	~FfmpegInputStream() noexcept {
-		avio_close(h);
-	}
-
 	/* virtual methods from InputStream */
 	bool IsEOF() noexcept override;
 	size_t Read(void *ptr, size_t size) override;
@@ -84,28 +77,20 @@ static InputStreamPtr
 input_ffmpeg_open(const char *uri,
 		  Mutex &mutex)
 {
-	AVIOContext *h;
-	auto result = avio_open(&h, uri, AVIO_FLAG_READ);
-	if (result != 0)
-		throw MakeFfmpegError(result);
-
-	return std::make_unique<FfmpegInputStream>(uri, mutex, h);
+	return std::make_unique<FfmpegInputStream>(uri, mutex);
 }
 
 size_t
 FfmpegInputStream::Read(void *ptr, size_t read_size)
 {
-	int result;
+	size_t result;
 
 	{
 		const ScopeUnlock unlock(mutex);
-		result = avio_read(h, (unsigned char *)ptr, read_size);
+		result = io.Read(ptr, read_size);
 	}
 
-	if (result <= 0) {
-		if (result < 0)
-			throw MakeFfmpegError(result, "avio_read() failed");
-
+	if (result == 0) {
 		eof = true;
 		return 0;
 	}
@@ -123,16 +108,13 @@ FfmpegInputStream::IsEOF() noexcept
 void
 FfmpegInputStream::Seek(offset_type new_offset)
 {
-	int64_t result;
+	uint64_t result;
 
 	{
 		const ScopeUnlock unlock(mutex);
-		result = avio_seek(h, new_offset, SEEK_SET);
+		result = io.Seek(new_offset);
 	}
 
-	if (result < 0)
-		throw MakeFfmpegError(result, "avio_seek() failed");
-
 	offset = result;
 	eof = false;
 }
diff --git a/src/lib/ffmpeg/IOContext.hxx b/src/lib/ffmpeg/IOContext.hxx
new file mode 100644
index 000000000..95ce637ee
--- /dev/null
+++ b/src/lib/ffmpeg/IOContext.hxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2003-2019 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 MPD_FFMPEG_IO_CONTEXT_HXX
+#define MPD_FFMPEG_IO_CONTEXT_HXX
+
+#include "util/Compiler.h"
+#include "Error.hxx"
+
+extern "C" {
+#include <libavformat/avio.h>
+}
+
+#include <utility>
+
+namespace Ffmpeg {
+
+class IOContext {
+	AVIOContext *io_context = nullptr;
+
+public:
+	IOContext() = default;
+
+	IOContext(const char *url, int flags) {
+		int err = avio_open(&io_context, url, flags);
+		if (err < 0)
+			throw MakeFfmpegError(err);
+	}
+
+	~IOContext() noexcept {
+		if (io_context != nullptr)
+			avio_close(io_context);
+	}
+
+	IOContext(IOContext &&src) noexcept
+		:io_context(std::exchange(src.io_context, nullptr)) {}
+
+	IOContext &operator=(IOContext &&src) noexcept {
+		using std::swap;
+		swap(io_context, src.io_context);
+		return *this;
+	}
+
+	AVIOContext &operator*() noexcept {
+		return *io_context;
+	}
+
+	AVIOContext *operator->() noexcept {
+		return io_context;
+	}
+
+	gcc_pure
+	auto GetSize() const noexcept {
+		return avio_size(io_context);
+	}
+
+	size_t Read(void *buffer, size_t size) {
+		int result = avio_read(io_context,
+				       (unsigned char *)buffer, size);
+		if (result < 0)
+			throw MakeFfmpegError(result, "avio_read() failed");
+
+		return result;
+	}
+
+	uint64_t Seek(uint64_t offset) {
+		int64_t result = avio_seek(io_context, offset, SEEK_SET);
+		if (result < 0)
+			throw MakeFfmpegError(result, "avio_seek() failed");
+
+		return result;
+	}
+};
+
+} // namespace Ffmpeg
+
+#endif