changeset 96:63bde7ca7ad0 trunk

[svn] First attempt at porting our FLAC plugin to API 1.1.3 (completely incompatible, as usual!). Needs more work and a version-sensitize FLAC checker.
author chainsaw
date Sat, 21 Oct 2006 09:21:12 -0700
parents b5a1b762f586
children a19f24790f3c
files ChangeLog src/flac/Makefile.lite src/flac/fileinfo.c src/flac/plugin.c
diffstat 4 files changed, 277 insertions(+), 383 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Oct 20 00:59:45 2006 -0700
+++ b/ChangeLog	Sat Oct 21 09:21:12 2006 -0700
@@ -1,3 +1,14 @@
+2006-10-20 07:59:45 +0000  Yoshiki Yazawa <yaz@cc.rim.or.jp>
+  revision [190]
+  - xspf now uses url encoding for location entry.
+  
+  trunk/src/xspf/Makefile    |    2 
+  trunk/src/xspf/urlencode.c |  158 +++++++++++++++++++++++++++++++++++++++++++++
+  trunk/src/xspf/urlencode.h |    4 +
+  trunk/src/xspf/xspf.c      |   52 +++++---------
+  4 files changed, 182 insertions(+), 34 deletions(-)
+
+
 2006-10-16 18:40:44 +0000  Giacomo Lozito <james@develia.org>
   revision [188]
   - do not use a different DATA_DIR for audacious-plugins
--- a/src/flac/Makefile.lite	Fri Oct 20 00:59:45 2006 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-#  libxmms-flac - XMMS FLAC input plugin
-#  Copyright (C) 2000,2001,2002,2003,2004,2005  Josh Coalson
-#
-#  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.
-
-#
-# GNU makefile
-#
-
-topdir = ../..
-
-LIB_NAME  = libxmms-flac
-INCLUDES  = $(shell xmms-config --cflags) -I./include -I$(topdir)/include -I..
-# refer to the static libs explicitly
-LIBS = $(topdir)/obj/$(BUILD)/lib/libFLAC.a $(topdir)/obj/$(BUILD)/lib/libplugin_common.a $(topdir)/obj/$(BUILD)/lib/libgrabbag.a $(topdir)/obj/$(BUILD)/lib/libreplaygain_analysis.a $(topdir)/obj/$(BUILD)/lib/libreplaygain_synthesis.a -L$(ICONV_LIB_DIR) -liconv -lstdc++ -lz
-
-SRCS_C = \
-	charset.c \
-	configure.c \
-	plugin.c \
-	fileinfo.c \
-	http.c \
-	tag.c
-
-include $(topdir)/build/lib.mk
-
-# DO NOT DELETE THIS LINE -- make depend depends on it.
--- a/src/flac/fileinfo.c	Fri Oct 20 00:59:45 2006 -0700
+++ b/src/flac/fileinfo.c	Sat Oct 21 09:21:12 2006 -0700
@@ -1,7 +1,7 @@
 /*  XMMS - Cross-platform multimedia player
  *  Copyright (C) 1998-2000  Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies
  *  Copyright (C) 1999,2000  Håvard Kvålen
- *  Copyright (C) 2002,2003,2004,2005  Daisuke Shimamura
+ *  Copyright (C) 2002,2003,2004,2005,2006  Daisuke Shimamura
  *
  *  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
@@ -30,13 +30,15 @@
 #include "FLAC/metadata.h"
 #include "charset.h"
 #include "configure.h"
+#include "plugin_common/locale_hack.h"
+#include "plugin_common/replaygain.h"
 #include "plugin_common/tags.h"
-#include "plugin_common/locale_hack.h"
 
 static GtkWidget *window = NULL;
 static GList *genre_list = NULL;
 static GtkWidget *filename_entry, *tag_frame;
 static GtkWidget *title_entry, *artist_entry, *album_entry, *date_entry, *tracknum_entry, *comment_entry;
+static GtkWidget *replaygain_reference, *replaygain_track_gain, *replaygain_album_gain, *replaygain_track_peak, *replaygain_album_peak;
 static GtkWidget *genre_combo;
 static GtkWidget *flac_samplerate, *flac_channels, *flac_bits_per_sample, *flac_blocksize, *flac_filesize, *flac_samples, *flac_bitrate;
 
@@ -226,7 +228,11 @@
 				(int)(streaminfo.data.stream_info.total_samples / streaminfo.data.stream_info.sample_rate % 60));
 
 	if(!stat(current_filename, &_stat) && S_ISREG(_stat.st_mode)) {
+#if _FILE_OFFSET_BITS == 64
+		label_set_text(flac_filesize, _("Filesize: %lld B"), _stat.st_size);
+#else
 		label_set_text(flac_filesize, _("Filesize: %ld B"), _stat.st_size);
+#endif
 		if (streaminfo.data.stream_info.total_samples)
 			label_set_text(flac_bitrate, _("Avg. bitrate: %.1f kb/s\nCompression ratio: %.1f%%"),
 					8.0 * (float)(_stat.st_size) / (1000.0 * (float)streaminfo.data.stream_info.total_samples / (float)streaminfo.data.stream_info.sample_rate),
@@ -234,6 +240,41 @@
 	}
 }
 
+static void show_replaygain()
+{
+	/* known limitation: If only one of gain and peak is set, neither will be shown. This is true for
+	 * both track and album replaygain tags. Written so it will be easy to fix, with some trouble.  */
+
+	gtk_label_set_text(GTK_LABEL(replaygain_reference), "");
+	gtk_label_set_text(GTK_LABEL(replaygain_track_gain), "");
+	gtk_label_set_text(GTK_LABEL(replaygain_album_gain), "");
+	gtk_label_set_text(GTK_LABEL(replaygain_track_peak), "");
+	gtk_label_set_text(GTK_LABEL(replaygain_album_peak), "");
+
+	double reference, track_gain, track_peak, album_gain, album_peak;
+	FLAC__bool reference_set, track_gain_set, track_peak_set, album_gain_set, album_peak_set;
+
+	FLAC_plugin__replaygain_get_from_file(
+		current_filename,
+		&reference, &reference_set,
+		&track_gain, &track_gain_set,
+		&album_gain, &album_gain_set,
+		&track_peak, &track_peak_set,
+		&album_peak, &album_peak_set
+	);
+
+	if(reference_set)
+		label_set_text(replaygain_reference, _("ReplayGain Reference Loudness: %2.1f dB"), reference);
+	if(track_gain_set)
+		label_set_text(replaygain_track_gain, _("ReplayGain Track Gain: %+2.2f dB"), track_gain);
+	if(album_gain_set)
+		label_set_text(replaygain_album_gain, _("ReplayGain Album Gain: %+2.2f dB"), album_gain);
+	if(track_peak_set)
+		label_set_text(replaygain_track_peak, _("ReplayGain Track Peak: %1.8f"), track_peak);
+	if(album_peak_set)
+		label_set_text(replaygain_album_peak, _("ReplayGain Album Peak: %1.8f"), album_peak);
+}
+
 void FLAC_XMMS__file_info_box(char *filename)
 {
 	unsigned i;
@@ -403,6 +444,31 @@
 		gtk_label_set_justify(GTK_LABEL(flac_bitrate), GTK_JUSTIFY_LEFT);
 		gtk_box_pack_start(GTK_BOX(flac_box), flac_bitrate, FALSE, FALSE, 0);
 
+		replaygain_reference = gtk_label_new("");
+		gtk_misc_set_alignment(GTK_MISC(replaygain_reference), 0, 0);
+		gtk_label_set_justify(GTK_LABEL(replaygain_reference), GTK_JUSTIFY_LEFT);
+		gtk_box_pack_start(GTK_BOX(flac_box), replaygain_reference, FALSE, FALSE, 0);
+
+		replaygain_track_gain = gtk_label_new("");
+		gtk_misc_set_alignment(GTK_MISC(replaygain_track_gain), 0, 0);
+		gtk_label_set_justify(GTK_LABEL(replaygain_track_gain), GTK_JUSTIFY_LEFT);
+		gtk_box_pack_start(GTK_BOX(flac_box), replaygain_track_gain, FALSE, FALSE, 0);
+
+		replaygain_album_gain = gtk_label_new("");
+		gtk_misc_set_alignment(GTK_MISC(replaygain_album_gain), 0, 0);
+		gtk_label_set_justify(GTK_LABEL(replaygain_album_gain), GTK_JUSTIFY_LEFT);
+		gtk_box_pack_start(GTK_BOX(flac_box), replaygain_album_gain, FALSE, FALSE, 0);
+
+		replaygain_track_peak = gtk_label_new("");
+		gtk_misc_set_alignment(GTK_MISC(replaygain_track_peak), 0, 0);
+		gtk_label_set_justify(GTK_LABEL(replaygain_track_peak), GTK_JUSTIFY_LEFT);
+		gtk_box_pack_start(GTK_BOX(flac_box), replaygain_track_peak, FALSE, FALSE, 0);
+
+		replaygain_album_peak = gtk_label_new("");
+		gtk_misc_set_alignment(GTK_MISC(replaygain_album_peak), 0, 0);
+		gtk_label_set_justify(GTK_LABEL(replaygain_album_peak), GTK_JUSTIFY_LEFT);
+		gtk_box_pack_start(GTK_BOX(flac_box), replaygain_album_peak, FALSE, FALSE, 0);
+
 		gtk_widget_show_all(window);
 	}
 
@@ -428,6 +494,7 @@
 
 	show_tag();
 	show_file_info();
+	show_replaygain();
 
 	gtk_widget_set_sensitive(tag_frame, TRUE);
 }
--- a/src/flac/plugin.c	Fri Oct 20 00:59:45 2006 -0700
+++ b/src/flac/plugin.c	Sat Oct 21 09:21:12 2006 -0700
@@ -1,5 +1,5 @@
 /* libxmms-flac - XMMS FLAC input plugin
- * Copyright (C) 2000,2001,2002,2003,2004,2005  Josh Coalson
+ * Copyright (C) 2000,2001,2002,2003,2004,2005,2006  Josh Coalson
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -16,6 +16,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
@@ -53,25 +54,19 @@
 #endif
 #define min(x,y) ((x)<(y)?(x):(y))
 
-/* adjust for compilers that can't understand using LLU suffix for uint64_t literals */
-#ifdef _MSC_VER
-#define FLAC__U64L(x) x
-#else
-#define FLAC__U64L(x) x##LLU
-#endif
-
 extern void FLAC_XMMS__file_info_box(char *filename);
 
 typedef struct {
 	FLAC__bool abort_flag;
 	FLAC__bool is_playing;
+	FLAC__bool is_http_source;
 	FLAC__bool eof;
 	FLAC__bool play_thread_open; /* if true, is_playing must also be true */
-	unsigned total_samples;
+	FLAC__uint64 total_samples;
 	unsigned bits_per_sample;
 	unsigned channels;
 	unsigned sample_rate;
-	unsigned length_in_msec;
+	int length_in_msec; /* int (instead of FLAC__uint64) only because that's what XMMS uses; seeking won't work right if this maxes out */
 	gchar *title;
 	AFormat sample_format;
 	unsigned sample_format_bytes_per_sample;
@@ -79,36 +74,7 @@
 	FLAC__bool has_replaygain;
 	double replay_scale;
 	DitherContext dither_context;
-} file_info_struct;
-
-typedef FLAC__StreamDecoderWriteStatus (*WriteCallback) (const void *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data);
-typedef void (*MetadataCallback) (const void *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
-typedef void (*ErrorCallback) (const void *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
-
-typedef struct {
-	FLAC__bool seekable;
-	void* (*new_decoder) (void);
-	FLAC__bool (*set_md5_checking) (void *decoder, FLAC__bool value);
-	FLAC__bool (*set_source) (void *decoder, const char* source);
-	FLAC__bool (*set_metadata_ignore_all) (void *decoder);
-	FLAC__bool (*set_metadata_respond) (void *decoder, FLAC__MetadataType type);
-	FLAC__bool (*set_write_callback) (void *decoder, WriteCallback value);
-	FLAC__bool (*set_metadata_callback) (void *decoder, MetadataCallback value);
-	FLAC__bool (*set_error_callback) (void *decoder, ErrorCallback value);
-	FLAC__bool (*set_client_data) (void *decoder, void *value);
-	FLAC__bool (*decoder_init) (void *decoder);
-	void (*safe_decoder_finish) (void *decoder);
-	void (*safe_decoder_delete) (void *decoder);
-	FLAC__bool (*process_until_end_of_metadata) (void *decoder);
-	FLAC__bool (*process_single) (void *decoder);
-	FLAC__bool (*is_eof) (void *decoder);
-} decoder_funcs_t;
-
-#define NUM_DECODER_TYPES 2
-typedef enum {
-	DECODER_FILE,
-	DECODER_HTTP
-} decoder_t;
+} stream_data_struct;
 
 static void FLAC_XMMS__init();
 static int  FLAC_XMMS__is_our_file(char *filename);
@@ -122,15 +88,14 @@
 
 static void *play_loop_(void *arg);
 
-static FLAC__bool safe_decoder_init_(const char *filename, void **decoderp, decoder_funcs_t const ** fnsp);
-static void file_decoder_safe_decoder_finish_(void *decoder);
-static void file_decoder_safe_decoder_delete_(void *decoder);
-static FLAC__StreamDecoderWriteStatus write_callback_(const void *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data);
-static void metadata_callback_(const void *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
-static void error_callback_(const void *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
+static FLAC__bool safe_decoder_init_(const char *filename, FLAC__StreamDecoder *decoder);
+static void safe_decoder_finish_(FLAC__StreamDecoder *decoder);
+static void safe_decoder_delete_(FLAC__StreamDecoder *decoder);
 
-static void init_decoder_func_tables();
-static decoder_t source_to_decoder_type (const char *source);
+static FLAC__StreamDecoderReadStatus http_read_callback_(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data);
+static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data);
+static void metadata_callback_(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
+static void error_callback_(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
 
 InputPlugin flac_ip =
 {
@@ -166,8 +131,8 @@
 static FLAC__byte sample_buffer_[SAMPLE_BUFFER_SIZE];
 static unsigned sample_buffer_first_, sample_buffer_last_;
 
-static void *decoder_ = 0, *decoder2 = 0;
-static file_info_struct file_info_;
+static FLAC__StreamDecoder *decoder_ = 0, *decoder2 = 0;
+static stream_data_struct stream_data_;
 static GThread *decode_thread_;
 static FLAC__bool audio_error_ = false;
 static FLAC__bool is_big_endian_host_;
@@ -177,11 +142,9 @@
 #define BITRATE_HIST_SIZE 50
 static unsigned bitrate_history_[BITRATE_HIST_SIZE];
 
-/* A table of sets of decoder functions, indexed by decoder_t */
-static const decoder_funcs_t* DECODER_FUNCS[NUM_DECODER_TYPES];
-
-static decoder_funcs_t const * decoder_func_table_, * decoder_func_table2;
-
+#ifdef SUPPORT_ATTRIBUTE_VISIBILITY
+InputPlugin *get_iplugin_info() __attribute__((visibility("default")));
+#endif
 
 InputPlugin *get_iplugin_info()
 {
@@ -191,8 +154,8 @@
 
 void set_track_info(const char* title, int length_in_msec)
 {
-	if (file_info_.is_playing) {
-		flac_ip.set_info((char*) title, length_in_msec, file_info_.sample_rate * file_info_.channels * file_info_.bits_per_sample, file_info_.sample_rate, file_info_.channels);
+	if (stream_data_.is_playing) {
+		flac_ip.set_info((char*) title, length_in_msec, stream_data_.sample_rate * stream_data_.channels * stream_data_.bits_per_sample, stream_data_.sample_rate, stream_data_.channels);
 	}
 }
 
@@ -214,15 +177,22 @@
 	return result;
 }
 
+static FLAC__bool is_http_source(const char *source)
+{
+	return 0 == strncasecmp(source, "http://", 7);
+}
+
 void FLAC_XMMS__init()
 {
 	ConfigDb *db;
 	FLAC__uint32 test = 1;
-	gchar *tmp = NULL;
 
 	is_big_endian_host_ = (*((FLAC__byte*)(&test)))? false : true;
 
-	memset(&flac_cfg, 0, sizeof(flac_cfg));
+	flac_cfg.title.tag_override = FALSE;
+	if (flac_cfg.title.tag_format)
+		g_free(flac_cfg.title.tag_format);
+	flac_cfg.title.convert_char_set = FALSE;
 
 	db = bmp_cfg_db_open();
 
@@ -275,21 +245,15 @@
 	bmp_cfg_db_get_bool(db, "flac", "stream.cast_title_streaming", &flac_cfg.stream.cast_title_streaming);
 	bmp_cfg_db_get_bool(db, "flac", "stream.use_udp_channel", &flac_cfg.stream.use_udp_channel);
 
-	init_decoder_func_tables();
- 	decoder_func_table_ = DECODER_FUNCS [DECODER_FILE];
-	decoder_ = decoder_func_table_ -> new_decoder();
-
 	bmp_cfg_db_get_bool(db, NULL, "use_proxy", &flac_cfg.stream.use_proxy);
 	bmp_cfg_db_get_string(db, NULL, "proxy_host", &flac_cfg.stream.proxy_host);
 	bmp_cfg_db_get_string(db, NULL, "proxy_port", &tmp);
 
-	if (tmp != NULL)
-		flac_cfg.stream.proxy_port = atoi(tmp);
-
 	bmp_cfg_db_get_bool(db, NULL, "proxy_use_auth", &flac_cfg.stream.proxy_use_auth);
 	bmp_cfg_db_get_string(db, NULL, "proxy_user", &flac_cfg.stream.proxy_user);
 	bmp_cfg_db_get_string(db, NULL, "proxy_pass", &flac_cfg.stream.proxy_pass);
 
+	decoder_ = FLAC__stream_decoder_new();
 	bmp_cfg_db_close(db);
 }
 
@@ -298,19 +262,19 @@
 	FILE *f;
 	FLAC__StreamMetadata streaminfo;
 
-        if (source_to_decoder_type (filename) == DECODER_FILE) {
-                if(0 == (f = fopen(filename, "r")))
-                        return 0;
-                fclose(f);
-	        if(!FLAC__metadata_get_streaminfo(filename, &streaminfo))
+	if(!is_http_source(filename)) {
+		if(0 == (f = fopen(filename, "r")))
 			return 0;
-		return 1;
-        }
+		fclose(f);
+		if(FLAC__metadata_get_streaminfo(filename, &streaminfo))
+			return 1;
+		return 0;
+	}
 
-	if(!safe_decoder_init_(filename, &decoder2, &decoder_func_table2))
+	if(!safe_decoder_init_(filename, decoder2))
 		return 0;
 
-	decoder_func_table2 -> safe_decoder_finish(decoder2);	
+	safe_decoder_finish_(decoder2);   
 	return 1;
 }
 
@@ -320,13 +284,14 @@
 
 	sample_buffer_first_ = sample_buffer_last_ = 0;
 	audio_error_ = false;
-	file_info_.abort_flag = false;
-	file_info_.is_playing = false;
-	file_info_.eof = false;
-	file_info_.play_thread_open = false;
-	file_info_.has_replaygain = false;
+	stream_data_.abort_flag = false;
+	stream_data_.is_playing = false;
+	stream_data_.is_http_source = is_http_source(filename);
+	stream_data_.eof = false;
+	stream_data_.play_thread_open = false;
+	stream_data_.has_replaygain = false;
 
-	if (source_to_decoder_type (filename) == DECODER_FILE) {
+	if(!is_http_source(filename)) {
 		if(0 == (f = fopen(filename, "r")))
 			return;
 		fclose(f);
@@ -335,68 +300,68 @@
 	if(decoder_ == 0)
 		return;
 
-	if(!safe_decoder_init_(filename, &decoder_, &decoder_func_table_))
+	if(!safe_decoder_init_(filename, decoder_))
 		return;
 
-	if(file_info_.has_replaygain && flac_cfg.output.replaygain.enable) {
+	if(stream_data_.has_replaygain && flac_cfg.output.replaygain.enable) {
 		if(flac_cfg.output.resolution.replaygain.bps_out == 8) {
-			file_info_.sample_format = FMT_U8;
-			file_info_.sample_format_bytes_per_sample = 1;
+			stream_data_.sample_format = FMT_U8;
+			stream_data_.sample_format_bytes_per_sample = 1;
 		}
 		else if(flac_cfg.output.resolution.replaygain.bps_out == 16) {
-			file_info_.sample_format = (is_big_endian_host_) ? FMT_S16_BE : FMT_S16_LE;
-			file_info_.sample_format_bytes_per_sample = 2;
+			stream_data_.sample_format = (is_big_endian_host_) ? FMT_S16_BE : FMT_S16_LE;
+			stream_data_.sample_format_bytes_per_sample = 2;
 		}
 		else {
 			/*@@@ need some error here like wa2: MessageBox(mod_.hMainWindow, "ERROR: plugin can only handle 8/16-bit samples\n", "ERROR: plugin can only handle 8/16-bit samples", 0); */
 			fprintf(stderr, "libxmms-flac: can't handle %d bit output\n", flac_cfg.output.resolution.replaygain.bps_out);
-			decoder_func_table_ -> safe_decoder_finish(decoder_);
+			safe_decoder_finish_(decoder_);
 			return;
 		}
 	}
 	else {
-		if(file_info_.bits_per_sample == 8) {
-			file_info_.sample_format = FMT_U8;
-			file_info_.sample_format_bytes_per_sample = 1;
+		if(stream_data_.bits_per_sample == 8) {
+			stream_data_.sample_format = FMT_U8;
+			stream_data_.sample_format_bytes_per_sample = 1;
 		}
-		else if(file_info_.bits_per_sample == 16 || (file_info_.bits_per_sample == 24 && flac_cfg.output.resolution.normal.dither_24_to_16)) {
-			file_info_.sample_format = (is_big_endian_host_) ? FMT_S16_BE : FMT_S16_LE;
-			file_info_.sample_format_bytes_per_sample = 2;
+		else if(stream_data_.bits_per_sample == 16 || (stream_data_.bits_per_sample == 24 && flac_cfg.output.resolution.normal.dither_24_to_16)) {
+			stream_data_.sample_format = (is_big_endian_host_) ? FMT_S16_BE : FMT_S16_LE;
+			stream_data_.sample_format_bytes_per_sample = 2;
 		}
 		else {
 			/*@@@ need some error here like wa2: MessageBox(mod_.hMainWindow, "ERROR: plugin can only handle 8/16-bit samples\n", "ERROR: plugin can only handle 8/16-bit samples", 0); */
-			fprintf(stderr, "libxmms-flac: can't handle %d bit output\n", file_info_.bits_per_sample);
-			decoder_func_table_ -> safe_decoder_finish(decoder_);
+			fprintf(stderr, "libxmms-flac: can't handle %d bit output\n", stream_data_.bits_per_sample);
+			safe_decoder_finish_(decoder_);
 			return;
 		}
 	}
-	FLAC__replaygain_synthesis__init_dither_context(&file_info_.dither_context, file_info_.sample_format_bytes_per_sample * 8, flac_cfg.output.resolution.replaygain.noise_shaping);
-	file_info_.is_playing = true;
+	FLAC__replaygain_synthesis__init_dither_context(&stream_data_.dither_context, stream_data_.sample_format_bytes_per_sample * 8, flac_cfg.output.resolution.replaygain.noise_shaping);
+	stream_data_.is_playing = true;
 
-	if(flac_ip.output->open_audio(file_info_.sample_format, file_info_.sample_rate, file_info_.channels) == 0) {
+	if(flac_ip.output->open_audio(stream_data_.sample_format, stream_data_.sample_rate, stream_data_.channels) == 0) {
 		audio_error_ = true;
-		decoder_func_table_ -> safe_decoder_finish(decoder_);
+		safe_decoder_finish_(decoder_);
 		return;
 	}
 
-	file_info_.title = flac_format_song_title(filename);
-	flac_ip.set_info(file_info_.title, file_info_.length_in_msec, file_info_.sample_rate * file_info_.channels * file_info_.bits_per_sample, file_info_.sample_rate, file_info_.channels);
+	stream_data_.title = flac_format_song_title(filename);
+	flac_ip.set_info(stream_data_.title, stream_data_.length_in_msec, stream_data_.sample_rate * stream_data_.channels * stream_data_.bits_per_sample, stream_data_.sample_rate, stream_data_.channels);
 
-	file_info_.seek_to_in_sec = -1;
-	file_info_.play_thread_open = true;
+	stream_data_.seek_to_in_sec = -1;
+	stream_data_.play_thread_open = true;
 	decode_thread_ = g_thread_create((GThreadFunc)play_loop_, NULL, TRUE, NULL);
 }
 
 void FLAC_XMMS__stop()
 {
-	if(file_info_.is_playing) {
-		file_info_.is_playing = false;
-		if(file_info_.play_thread_open) {
-			file_info_.play_thread_open = false;
+	if(stream_data_.is_playing) {
+		stream_data_.is_playing = false;
+		if(stream_data_.play_thread_open) {
+			stream_data_.play_thread_open = false;
 			g_thread_join(decode_thread_);
 		}
 		flac_ip.output->close_audio();
-		decoder_func_table_ -> safe_decoder_finish (decoder_);
+		safe_decoder_finish_(decoder_);
 	}
 }
 
@@ -407,11 +372,11 @@
 
 void FLAC_XMMS__seek(int time)
 {
-	if (decoder_func_table_->seekable) {
-		file_info_.seek_to_in_sec = time;
-		file_info_.eof = false;
+	if(!stream_data_.is_http_source) {
+		stream_data_.seek_to_in_sec = time;
+		stream_data_.eof = false;
 
-		while(file_info_.seek_to_in_sec != -1)
+		while(stream_data_.seek_to_in_sec != -1)
 			xmms_usleep(10000);
 	}
 }
@@ -420,7 +385,7 @@
 {
 	if(audio_error_)
 		return -2;
-	if(!file_info_.is_playing || (file_info_.eof && !flac_ip.output->buffer_playing()))
+	if(!stream_data_.is_playing || (stream_data_.eof && !flac_ip.output->buffer_playing()))
 		return -1;
 	else
 		return flac_ip.output->output_time();
@@ -457,7 +422,7 @@
         flac_cfg.stream.proxy_pass = NULL;
     }
 
-	decoder_func_table_ -> safe_decoder_delete(decoder_);
+	safe_decoder_delete_(decoder_);
 	decoder_ = 0;
 }
 
@@ -471,7 +436,7 @@
 	if(!FLAC__metadata_get_streaminfo(filename, &streaminfo)) {
 		/* @@@ how to report the error? */
 		if(title) {
-			if (source_to_decoder_type (filename) == DECODER_FILE) {
+			if (!is_http_source(filename)) {
 				static const char *errtitle = "Invalid FLAC File: ";
 				*title = g_malloc(strlen(errtitle) + 1 + strlen(filename) + 1 + 1);
 				sprintf(*title, "%s\"%s\"", errtitle, filename);
@@ -487,8 +452,12 @@
 	if(title) {
 		*title = flac_format_song_title(filename);
 	}
-	if(length_in_msec)
-		*length_in_msec = (unsigned)((double)streaminfo.data.stream_info.total_samples / (double)streaminfo.data.stream_info.sample_rate * 1000.0 + 0.5);
+	if(length_in_msec) {
+		FLAC__uint64 l = (FLAC__uint64)((double)streaminfo.data.stream_info.total_samples / (double)streaminfo.data.stream_info.sample_rate * 1000.0 + 0.5);
+		if (l > INT_MAX)
+			l = INT_MAX;
+		*length_in_msec = (int)l;
+	}
 }
 
 /***********************************************************************
@@ -502,40 +471,40 @@
 
 	(void)arg;
 
-	while(file_info_.is_playing) {
-		if(!file_info_.eof) {
+	while(stream_data_.is_playing) {
+		if(!stream_data_.eof) {
 			while(sample_buffer_last_ - sample_buffer_first_ < SAMPLES_PER_WRITE) {
 				unsigned s;
 
 				s = sample_buffer_last_ - sample_buffer_first_;
-				if(decoder_func_table_ -> is_eof(decoder_)) {
-					file_info_.eof = true;
+				if(FLAC__stream_decoder_get_state(decoder_) == FLAC__STREAM_DECODER_END_OF_STREAM) {
+					stream_data_.eof = true;
 					break;
 				}
-				else if (!decoder_func_table_ -> process_single(decoder_)) {
+				else if(!FLAC__stream_decoder_process_single(decoder_)) {
 					/*@@@ this should probably be a dialog */
 					fprintf(stderr, "libxmms-flac: READ ERROR processing frame\n");
-					file_info_.eof = true;
+					stream_data_.eof = true;
 					break;
 				}
 				blocksize = sample_buffer_last_ - sample_buffer_first_ - s;
 				decode_position_frame_last = decode_position_frame;
-				if(!decoder_func_table_->seekable || !FLAC__file_decoder_get_decode_position(decoder_, &decode_position_frame))
+				if(stream_data_.is_http_source || !FLAC__stream_decoder_get_decode_position(decoder_, &decode_position_frame))
 					decode_position_frame = 0;
 			}
 			if(sample_buffer_last_ - sample_buffer_first_ > 0) {
 				const unsigned n = min(sample_buffer_last_ - sample_buffer_first_, SAMPLES_PER_WRITE);
-				int bytes = n * file_info_.channels * file_info_.sample_format_bytes_per_sample;
-				FLAC__byte *sample_buffer_start = sample_buffer_ + sample_buffer_first_ * file_info_.channels * file_info_.sample_format_bytes_per_sample;
+				int bytes = n * stream_data_.channels * stream_data_.sample_format_bytes_per_sample;
+				FLAC__byte *sample_buffer_start = sample_buffer_ + sample_buffer_first_ * stream_data_.channels * stream_data_.sample_format_bytes_per_sample;
 				unsigned written_time, bh_index_w;
 				FLAC__uint64 decode_position;
 
 				sample_buffer_first_ += n;
-				while(flac_ip.output->buffer_free() < (int)bytes && file_info_.is_playing && file_info_.seek_to_in_sec == -1)
+				while(flac_ip.output->buffer_free() < (int)bytes && stream_data_.is_playing && stream_data_.seek_to_in_sec == -1)
 					xmms_usleep(10000);
-				if(file_info_.is_playing && file_info_.seek_to_in_sec == -1)
-					produce_audio(flac_ip.output->written_time(), file_info_.sample_format,
-						file_info_.channels, bytes, sample_buffer_start, NULL);
+				if(stream_data_.is_playing && stream_data_.seek_to_in_sec == -1)
+					produce_audio(flac_ip.output->written_time(), stream_data_.sample_format,
+						stream_data_.channels, bytes, sample_buffer_start, NULL);
 
 				/* compute current bitrate */
 
@@ -547,263 +516,145 @@
 					bitrate_history_[(bh_index_w + BITRATE_HIST_SIZE - 1) % BITRATE_HIST_SIZE] =
 						decode_position > decode_position_last && written_time > written_time_last ?
 							8000 * (decode_position - decode_position_last) / (written_time - written_time_last) :
-							file_info_.sample_rate * file_info_.channels * file_info_.bits_per_sample;
+							stream_data_.sample_rate * stream_data_.channels * stream_data_.bits_per_sample;
 					decode_position_last = decode_position;
 					written_time_last = written_time;
 				}
 			}
 			else {
-				file_info_.eof = true;
+				stream_data_.eof = true;
 				xmms_usleep(10000);
 			}
 		}
 		else
 			xmms_usleep(10000);
-		if(decoder_func_table_->seekable && file_info_.seek_to_in_sec != -1) {
-			const double distance = (double)file_info_.seek_to_in_sec * 1000.0 / (double)file_info_.length_in_msec;
-			unsigned target_sample = (unsigned)(distance * (double)file_info_.total_samples);
-			if(FLAC__file_decoder_seek_absolute(decoder_, (FLAC__uint64)target_sample)) {
-				flac_ip.output->flush(file_info_.seek_to_in_sec * 1000);
+		if(!stream_data_.is_http_source && stream_data_.seek_to_in_sec != -1) {
+			const double distance = (double)stream_data_.seek_to_in_sec * 1000.0 / (double)stream_data_.length_in_msec;
+			FLAC__uint64 target_sample = (FLAC__uint64)(distance * (double)stream_data_.total_samples);
+			if(stream_data_.total_samples > 0 && target_sample >= stream_data_.total_samples)
+				target_sample = stream_data_.total_samples - 1;
+			if(FLAC__stream_decoder_seek_absolute(decoder_, target_sample)) {
+				flac_ip.output->flush(stream_data_.seek_to_in_sec * 1000);
 				bh_index_last_w = bh_index_last_o = flac_ip.output->output_time() / BITRATE_HIST_SEGMENT_MSEC % BITRATE_HIST_SIZE;
-				if(!FLAC__file_decoder_get_decode_position(decoder_, &decode_position_frame))
+				if(!FLAC__stream_decoder_get_decode_position(decoder_, &decode_position_frame))
 					decode_position_frame = 0;
-				file_info_.seek_to_in_sec = -1;
-				file_info_.eof = false;
+				stream_data_.eof = false;
 				sample_buffer_first_ = sample_buffer_last_ = 0;
 			}
+			else if(FLAC__stream_decoder_get_state(decoder_) == FLAC__STREAM_DECODER_SEEK_ERROR) {
+				/*@@@ this should probably be a dialog */
+				fprintf(stderr, "libxmms-flac: SEEK ERROR\n");
+				FLAC__stream_decoder_flush(decoder_);
+				stream_data_.eof = false;
+				sample_buffer_first_ = sample_buffer_last_ = 0;
+			}
+			stream_data_.seek_to_in_sec = -1;
 		}
 		else {
 			/* display the right bitrate from history */
 			unsigned bh_index_o = flac_ip.output->output_time() / BITRATE_HIST_SEGMENT_MSEC % BITRATE_HIST_SIZE;
 			if(bh_index_o != bh_index_last_o && bh_index_o != bh_index_last_w && bh_index_o != (bh_index_last_w + 1) % BITRATE_HIST_SIZE) {
 				bh_index_last_o = bh_index_o;
-				flac_ip.set_info(file_info_.title, file_info_.length_in_msec, bitrate_history_[bh_index_o], file_info_.sample_rate, file_info_.channels);
+				flac_ip.set_info(stream_data_.title, stream_data_.length_in_msec, bitrate_history_[bh_index_o], stream_data_.sample_rate, stream_data_.channels);
 			}
 		}
 	}
 
-	decoder_func_table_ -> safe_decoder_finish(decoder_);
+	safe_decoder_finish_(decoder_);
 
 	/* are these two calls necessary? */
 	flac_ip.output->buffer_free();
 	flac_ip.output->buffer_free();
 
-	g_free(file_info_.title);
+	g_free(stream_data_.title);
 
 	g_thread_exit(NULL);
 	return 0; /* to silence the compiler warning about not returning a value */
 }
 
-/*********** File decoder functions */
-
-static FLAC__bool file_decoder_init (void *decoder)
-{
-	return FLAC__file_decoder_init( (FLAC__FileDecoder*) decoder) == FLAC__FILE_DECODER_OK;
-}
-
-static void file_decoder_safe_decoder_finish_(void *decoder)
-{
-	if(decoder && FLAC__file_decoder_get_state((FLAC__FileDecoder *) decoder) != FLAC__FILE_DECODER_UNINITIALIZED)
-		FLAC__file_decoder_finish((FLAC__FileDecoder *) decoder);
-}
-
-static void file_decoder_safe_decoder_delete_(void *decoder)
-{
-	if(decoder) {
-		file_decoder_safe_decoder_finish_(decoder);
-		FLAC__file_decoder_delete( (FLAC__FileDecoder *) decoder);
-	}
-}
-
-static FLAC__bool file_decoder_is_eof(void *decoder)
-{
-	return FLAC__file_decoder_get_state((FLAC__FileDecoder *) decoder) == FLAC__FILE_DECODER_END_OF_FILE;
-}
-
-static const decoder_funcs_t FILE_DECODER_FUNCTIONS = {
-	true,
-	(void* (*) (void)) FLAC__file_decoder_new,
-	(FLAC__bool (*) (void *, FLAC__bool)) FLAC__file_decoder_set_md5_checking,
-	(FLAC__bool (*) (void *, const char*)) FLAC__file_decoder_set_filename,
-	(FLAC__bool (*) (void *)) FLAC__file_decoder_set_metadata_ignore_all,
-	(FLAC__bool (*) (void *, FLAC__MetadataType)) FLAC__file_decoder_set_metadata_respond,
-	(FLAC__bool (*) (void *, WriteCallback)) FLAC__file_decoder_set_write_callback,
-	(FLAC__bool (*) (void *, MetadataCallback)) FLAC__file_decoder_set_metadata_callback,
-	(FLAC__bool (*) (void *, ErrorCallback)) FLAC__file_decoder_set_error_callback,
-	(FLAC__bool (*) (void *, void *)) FLAC__file_decoder_set_client_data,
-	(FLAC__bool (*) (void *)) file_decoder_init,
-	(void (*) (void *)) file_decoder_safe_decoder_finish_,
-	(void (*) (void *)) file_decoder_safe_decoder_delete_,
-	(FLAC__bool (*) (void *)) FLAC__file_decoder_process_until_end_of_metadata,
-	(FLAC__bool (*) (void *)) FLAC__file_decoder_process_single,
-	file_decoder_is_eof
-};
-
-/*********** HTTP decoder functions */
-
-static gchar *url_;
-
-static FLAC__bool http_decoder_set_md5_checking (void *decoder, FLAC__bool value)
-{
-	(void) value;
-	// operation unsupported
-	return FLAC__stream_decoder_get_state ((const FLAC__StreamDecoder *) decoder) ==
-		FLAC__STREAM_DECODER_UNINITIALIZED;
-}
-
-static FLAC__bool http_decoder_set_url (void *decoder, const char* url)
-{
-	(void) decoder;
-	url_ = g_strdup (url);
-	return true;
-}
-
-static FLAC__StreamDecoderReadStatus http_decoder_read_callback (const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], unsigned *bytes, void *client_data)
+FLAC__bool safe_decoder_init_(const char *filename, FLAC__StreamDecoder *decoder)
 {
-	(void) decoder;
-	(void) client_data;
-	*bytes = flac_http_read (buffer, *bytes);
-	return *bytes ? FLAC__STREAM_DECODER_READ_STATUS_CONTINUE : FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
-}
-
-static FLAC__bool http_decoder_init (void *decoder)
-{
-	flac_http_open (url_, 0);
-	g_free (url_);
-	FLAC__stream_decoder_set_read_callback (decoder, http_decoder_read_callback);
-	return FLAC__stream_decoder_init( (FLAC__StreamDecoder*) decoder) == FLAC__STREAM_DECODER_SEARCH_FOR_METADATA;
-}
-
-static void http_decoder_safe_decoder_finish_(void *decoder)
-{
-	if(decoder && FLAC__stream_decoder_get_state((FLAC__StreamDecoder *) decoder) != FLAC__STREAM_DECODER_UNINITIALIZED) {
-		FLAC__stream_decoder_finish((FLAC__StreamDecoder *) decoder);
-		flac_http_close();
-	}
-}
-
-static void http_decoder_safe_decoder_delete_(void *decoder)
-{
-	if(decoder) {
-		http_decoder_safe_decoder_finish_(decoder);
-		FLAC__stream_decoder_delete( (FLAC__StreamDecoder *) decoder);
-	}
-}
-
-static FLAC__bool http_decoder_is_eof(void *decoder)
-{
-	return FLAC__stream_decoder_get_state((FLAC__StreamDecoder *) decoder) == FLAC__STREAM_DECODER_END_OF_STREAM;
-}
-
-static const decoder_funcs_t HTTP_DECODER_FUNCTIONS = {
-	false,
-	(void* (*) (void)) FLAC__stream_decoder_new,
-	http_decoder_set_md5_checking,
-	(FLAC__bool (*) (void *, const char*)) http_decoder_set_url,
-	(FLAC__bool (*) (void *)) FLAC__stream_decoder_set_metadata_ignore_all,
-	(FLAC__bool (*) (void *, FLAC__MetadataType)) FLAC__stream_decoder_set_metadata_respond,
-	(FLAC__bool (*) (void *, WriteCallback)) FLAC__stream_decoder_set_write_callback,
-	(FLAC__bool (*) (void *, MetadataCallback)) FLAC__stream_decoder_set_metadata_callback,
-	(FLAC__bool (*) (void *, ErrorCallback)) FLAC__stream_decoder_set_error_callback,
-	(FLAC__bool (*) (void *, void *)) FLAC__stream_decoder_set_client_data,
-	(FLAC__bool (*) (void *)) http_decoder_init,
-	(void (*) (void *)) http_decoder_safe_decoder_finish_,
-	(void (*) (void *)) http_decoder_safe_decoder_delete_,
-	(FLAC__bool (*) (void *)) FLAC__stream_decoder_process_until_end_of_metadata,
-	(FLAC__bool (*) (void *)) FLAC__stream_decoder_process_single,
-	http_decoder_is_eof
-};
-
-static decoder_funcs_t const *decoder_func_table_;
-
-static void init_decoder_func_tables()
-{
-	DECODER_FUNCS [DECODER_FILE] = & FILE_DECODER_FUNCTIONS;
-	DECODER_FUNCS [DECODER_HTTP] = & HTTP_DECODER_FUNCTIONS;
-}
-
-static decoder_t source_to_decoder_type (const char *source)
-{
-	return strncasecmp(source, "http://", 7) ? DECODER_FILE : DECODER_HTTP;
-}
-
-static void change_decoder_if_needed (decoder_t new_decoder_type, void **decoderp, decoder_funcs_t const ** fntabp)
-{
-	const decoder_funcs_t *new_fn_table = DECODER_FUNCS [new_decoder_type];
-	if (*fntabp != new_fn_table) {
-		(*fntabp)->safe_decoder_delete(*decoderp);
-		*fntabp = new_fn_table;
-		*decoderp = new_fn_table -> new_decoder();
-	}
-}
-
-FLAC__bool safe_decoder_init_(const char *filename, void **decoderp, decoder_funcs_t const ** fntabp)
-{
-	if(decoderp == 0 || *decoderp == 0)
+	if(decoder == 0)
 		return false;
 
-	(*fntabp)->safe_decoder_finish(*decoderp);
-
-	change_decoder_if_needed(source_to_decoder_type(filename), decoderp, fntabp);
-
-	{
-		decoder_funcs_t const *fntab = *fntabp;
-		void *decoder = *decoderp;
-
-		decoder = *decoderp;
-		fntab = *fntabp;
+	safe_decoder_finish_(decoder);
 
-		fntab -> set_md5_checking(decoder, false);
-		fntab -> set_source(decoder, filename);
-		fntab -> set_metadata_ignore_all(decoder);
-		fntab -> set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO);
-		fntab -> set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);
-		fntab -> set_write_callback(decoder, write_callback_);
-		fntab -> set_metadata_callback(decoder, metadata_callback_);
-		fntab -> set_error_callback(decoder, error_callback_);
-		fntab -> set_client_data(decoder, &file_info_);
-		if(!fntab -> decoder_init(decoder))
-			return false;
-
-		if(!fntab -> process_until_end_of_metadata(decoder))
+	FLAC__stream_decoder_set_md5_checking(decoder, false);
+	FLAC__stream_decoder_set_metadata_ignore_all(decoder);
+	FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO);
+	FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);
+	if(stream_data_.is_http_source) {
+		flac_http_open(filename, 0);
+		if(FLAC__stream_decoder_init_stream(decoder, http_read_callback_, /*seek_callback=*/0, /*tell_callback=*/0, /*length_callback=*/0, /*eof_callback=*/0, write_callback_, metadata_callback_, error_callback_, /*client_data=*/&stream_data_) != FLAC__STREAM_DECODER_INIT_STATUS_OK)
 			return false;
 	}
+	else {
+		if(FLAC__stream_decoder_init_file(decoder, filename, write_callback_, metadata_callback_, error_callback_, /*client_data=*/&stream_data_) != FLAC__STREAM_DECODER_INIT_STATUS_OK)
+			return false;
+	}
+
+	if(!FLAC__stream_decoder_process_until_end_of_metadata(decoder))
+		return false;
 
 	return true;
 }
 
-FLAC__StreamDecoderWriteStatus write_callback_(const void *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
+void safe_decoder_finish_(FLAC__StreamDecoder *decoder)
+{
+	if(decoder && FLAC__stream_decoder_get_state(decoder) != FLAC__STREAM_DECODER_UNINITIALIZED)
+		FLAC__stream_decoder_finish(decoder);
+	if(stream_data_.is_http_source)
+		flac_http_close();
+}
+
+void safe_decoder_delete_(FLAC__StreamDecoder *decoder)
 {
-	file_info_struct *file_info = (file_info_struct *)client_data;
-	const unsigned channels = file_info->channels, wide_samples = frame->header.blocksize;
-	const unsigned bits_per_sample = file_info->bits_per_sample;
+	if(decoder) {
+		safe_decoder_finish_(decoder);
+		FLAC__stream_decoder_delete(decoder);
+	}
+}
+
+FLAC__StreamDecoderReadStatus http_read_callback_(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data)
+{
+	(void)decoder;
+	(void)client_data;
+	*bytes = flac_http_read(buffer, *bytes);
+	return *bytes ? FLAC__STREAM_DECODER_READ_STATUS_CONTINUE : FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+}
+
+FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
+{
+	stream_data_struct *stream_data = (stream_data_struct *)client_data;
+	const unsigned channels = stream_data->channels, wide_samples = frame->header.blocksize;
+	const unsigned bits_per_sample = stream_data->bits_per_sample;
 	FLAC__byte *sample_buffer_start;
 
 	(void)decoder;
 
-	if(file_info->abort_flag)
+	if(stream_data->abort_flag)
 		return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
 
-	if((sample_buffer_last_ + wide_samples) > (SAMPLE_BUFFER_SIZE / (channels * file_info->sample_format_bytes_per_sample))) {
-		memmove(sample_buffer_, sample_buffer_ + sample_buffer_first_ * channels * file_info->sample_format_bytes_per_sample, (sample_buffer_last_ - sample_buffer_first_) * channels * file_info->sample_format_bytes_per_sample);
+	if((sample_buffer_last_ + wide_samples) > (SAMPLE_BUFFER_SIZE / (channels * stream_data->sample_format_bytes_per_sample))) {
+		memmove(sample_buffer_, sample_buffer_ + sample_buffer_first_ * channels * stream_data->sample_format_bytes_per_sample, (sample_buffer_last_ - sample_buffer_first_) * channels * stream_data->sample_format_bytes_per_sample);
 		sample_buffer_last_ -= sample_buffer_first_;
 		sample_buffer_first_ = 0;
 	}
-	sample_buffer_start = sample_buffer_ + sample_buffer_last_ * channels * file_info->sample_format_bytes_per_sample;
-	if(file_info->has_replaygain && flac_cfg.output.replaygain.enable) {
+	sample_buffer_start = sample_buffer_ + sample_buffer_last_ * channels * stream_data->sample_format_bytes_per_sample;
+	if(stream_data->has_replaygain && flac_cfg.output.replaygain.enable) {
 		FLAC__replaygain_synthesis__apply_gain(
 				sample_buffer_start,
 				!is_big_endian_host_,
-				file_info->sample_format_bytes_per_sample == 1, /* unsigned_data_out */
+				stream_data->sample_format_bytes_per_sample == 1, /* unsigned_data_out */
 				buffer,
 				wide_samples,
 				channels,
 				bits_per_sample,
-				file_info->sample_format_bytes_per_sample * 8,
-				file_info->replay_scale,
+				stream_data->sample_format_bytes_per_sample * 8,
+				stream_data->replay_scale,
 				flac_cfg.output.replaygain.hard_limit,
 				flac_cfg.output.resolution.replaygain.dither,
-				&file_info->dither_context
+				&stream_data->dither_context
 		);
 	}
 	else if(is_big_endian_host_) {
@@ -813,7 +664,7 @@
 			wide_samples,
 			channels,
 			bits_per_sample,
-			file_info->sample_format_bytes_per_sample * 8
+			stream_data->sample_format_bytes_per_sample * 8
 		);
 	}
 	else {
@@ -823,7 +674,7 @@
 			wide_samples,
 			channels,
 			bits_per_sample,
-			file_info->sample_format_bytes_per_sample * 8
+			stream_data->sample_format_bytes_per_sample * 8
 		);
 	}
 
@@ -832,31 +683,35 @@
 	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
 }
 
-void metadata_callback_(const void *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
+void metadata_callback_(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
 {
-	file_info_struct *file_info = (file_info_struct *)client_data;
+	stream_data_struct *stream_data = (stream_data_struct *)client_data;
 	(void)decoder;
 	if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
-		FLAC__ASSERT(metadata->data.stream_info.total_samples < FLAC__U64L(0x100000000)); /* this plugin can only handle < 4 gigasamples */
-		file_info->total_samples = (unsigned)(metadata->data.stream_info.total_samples&0xffffffff);
-		file_info->bits_per_sample = metadata->data.stream_info.bits_per_sample;
-		file_info->channels = metadata->data.stream_info.channels;
-		file_info->sample_rate = metadata->data.stream_info.sample_rate;
-		file_info->length_in_msec = (unsigned)((double)file_info->total_samples / (double)file_info->sample_rate * 1000.0 + 0.5);
+		stream_data->total_samples = metadata->data.stream_info.total_samples;
+		stream_data->bits_per_sample = metadata->data.stream_info.bits_per_sample;
+		stream_data->channels = metadata->data.stream_info.channels;
+		stream_data->sample_rate = metadata->data.stream_info.sample_rate;
+		{
+			FLAC__uint64 l = (FLAC__uint64)((double)stream_data->total_samples / (double)stream_data->sample_rate * 1000.0 + 0.5);
+			if (l > INT_MAX)
+				l = INT_MAX;
+			stream_data->length_in_msec = (int)l;
+		}
 	}
 	else if(metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
-		double gain, peak;
-		if(grabbag__replaygain_load_from_vorbiscomment(metadata, flac_cfg.output.replaygain.album_mode, &gain, &peak)) {
-			file_info->has_replaygain = true;
-			file_info->replay_scale = grabbag__replaygain_compute_scale_factor(peak, gain, (double)flac_cfg.output.replaygain.preamp, /*prevent_clipping=*/!flac_cfg.output.replaygain.hard_limit);
+		double reference, gain, peak;
+		if(grabbag__replaygain_load_from_vorbiscomment(metadata, flac_cfg.output.replaygain.album_mode, /*strict=*/false, &reference, &gain, &peak)) {
+			stream_data->has_replaygain = true;
+			stream_data->replay_scale = grabbag__replaygain_compute_scale_factor(peak, gain, (double)flac_cfg.output.replaygain.preamp, /*prevent_clipping=*/!flac_cfg.output.replaygain.hard_limit);
 		}
 	}
 }
 
-void error_callback_(const void *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
+void error_callback_(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
 {
-	file_info_struct *file_info = (file_info_struct *)client_data;
+	stream_data_struct *stream_data = (stream_data_struct *)client_data;
 	(void)decoder;
 	if(status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC)
-		file_info->abort_flag = true;
+		stream_data->abort_flag = true;
 }