/*
 * GQmpeg
 * (C)1998, 1999 John Ellis
 *
 * Author: John Ellis
 *
 * This software is released under the GNU General Public License.
 * Please read the included file COPYING for more information.
 * This software comes with no warranty of any kind, use at you own risk!
 */

#include "io_mpg123.h"
#include "players.h"
#include "utildlg.h"

#include <errno.h>

/* ----------------------------------------------------------
   input / output interface to mpg123
   ----------------------------------------------------------*/

#define MPG123_VER_0_59O 0	/* versions 0.59o and 0.59p */
#define MPG123_VER_0_59Q 1	/* version 0.59q */
#define MPG123_VER_UNKNOWN 9	/* unknown, treat as latest in case newer ver */

#define MPG123_VER_STRING "0.59q"	/* text representation of latest version */

enum {
	MPG123_TYPE_FILE,
	MPG123_TYPE_HTTP
};

	/* mpg123 options */
gint		mpg123_buffer_enable = FALSE;
gint		mpg123_buffer = 512;
gint		mpg123_downsample = MPG123_DOWNSAMPLE_AUTO;
gint		mpg123_custom_sample_size = 44100;
gint		mpg123_mono = FALSE;
gint		mpg123_8bit = FALSE;
gint		mpg123_device_enable = FALSE;
gchar		*mpg123_device = NULL;
gint		mpg123_aggressive = FALSE;
gint		mpg123_use_freeamp = FALSE;

static FILE *mpgpipe = NULL;
static int mypipe[2];
static gchar pipebuf1[256];
static gchar *pipebuf_ptr;
static gint signal_timeout_id = 0;
static gint mpg123_version = MPG123_VER_0_59O;
static gint mpg123_force_freeamp = FALSE;
static gint ran_freeamp = FALSE;

static gint parse_header_info();
static void parse_frame_info();
static void stop_data();
static void song_finished();
static int parse_pipe_buffer();
static int check_pipe_for_data(int fd);
static gint read_data();
static void stop_playing_file();

static gint parse_header_info()
{
	gchar s[128];
	gchar *ptr;
	gchar *ptr2;

if (debug_mode) printf("h");

	ptr = pipebuf1;

	/* Version line */
	if (!strncmp(pipebuf1,"Version",7))
		{
if (debug_mode) putchar('V');
		ptr += 8;
		ptr2 = s;
		while (ptr[0] != ' ' && ptr[0] != '\0')
			{
			ptr2[0] = ptr[0];
			ptr2++;
			ptr++;
			}
		ptr2[0] = '\0';
		if (strcmp(s, "0.59o") == 0)
			mpg123_version = MPG123_VER_0_59O;
		else if (strcmp(s, "0.59p") == 0)
			mpg123_version = MPG123_VER_0_59O;
		else if (strcmp(s, "0.59q") == 0)
			mpg123_version = MPG123_VER_0_59Q;
		else if (strcmp(s, "0.59r") == 0)
			mpg123_version = MPG123_VER_0_59Q;
		else if (strncmp(s, "0.59o", 5) < 1)
			{
			static gint warning_done = FALSE;
			mpg123_version = MPG123_VER_0_59O;
			if (debug_mode || !warning_done)
				{
				printf("mpg123 version prior to 0.59o found!\n");
				printf("assuming 0.59o compatible (or freeamp)\n");
				printf("The version line reported was:\n%s\n", pipebuf1);
				}
			warning_done = TRUE;
			}
		else
			{
			static gint warning_done = FALSE;
			mpg123_version = MPG123_VER_UNKNOWN;
			if (debug_mode || !warning_done)
				{
				printf("unknown version of mpg123, assuming" MPG123_VER_STRING "compatible\n");
				printf("(GQmpeg requires mpg123 0.59o or later)\n");
				printf("The version line reported was:\n%s\n", pipebuf1);
				}
			warning_done = TRUE;
			}
		if (debug_mode) printf("mpg123 version detected: %d\n", mpg123_version);
		return TRUE;
		}

	/* MPEG, layer, freq, mode line */
	if (!strncmp(pipebuf1,"MPEG",4))
		{
if (debug_mode) putchar('M');
		ptr += 4;
		ptr2 = s;
		while (ptr[0] != 'L' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ' && ptr[0] != ',')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		mpeg_version = strtol(s,NULL,10);

		ptr += 6;
		ptr2 = s;
		while (ptr[0] != 'F' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ' && ptr[0] != ',')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		mpeg_layer = strlen(s);

		ptr += 5;
		ptr2 = s;
		while (ptr[0] != 'm' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ' && ptr[0] != ',')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		input_hz = strtol(s,NULL,10);

		ptr += 5;
		ptr2 = s;
		while (ptr[0] != ',' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		if (!strcmp(s,"Stereo")) mpeg_mode = 1;
		if (!strcmp(s,"Joint-Stereo")) mpeg_mode = 2;
		if (!strcmp(s,"Dual-Channel")) mpeg_mode = 3;
		if (!strcmp(s,"Single-Channel")) mpeg_mode = 4;
		return TRUE;
		}

	/* Channels  line */
	if (!strncmp(pipebuf1,"Channels:",9))
		{
if (debug_mode) putchar('C');
		ptr += 9;
		ptr2 = s;
		while (ptr[0] != ',' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		input_channels = strtol(s,NULL,10);
		return TRUE;
		}

	/* Bitrate line */
	if (!strncmp(pipebuf1,"Bitrate:",8))
		{
if (debug_mode) putchar('B');
		ptr += 8;
		ptr2 = s;
		while (ptr[0] != 'K' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		input_bitrate = strtol(s,NULL,10);
		return TRUE;
		}

	/* Audio output line */
	if (!strncmp(pipebuf1,"Audio:",6))
		{
if (debug_mode) putchar('A');
		ptr += 6;
		ptr2 = s;
		while (ptr[0] != ',' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
/*		output_conversion = strtol(s,NULL,10); never used anyway */

		ptr += 7;
		ptr2 = s;
		while (ptr[0] != 'e' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ' && ptr[0] != ',')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		output_hz = strtol(s,NULL,10);

		ptr += 9;
		ptr2 = s;
		while (ptr[0] != ',' && ptr[0] != '\0')
			{
			if (ptr[0] >= '0' && ptr[0] <= '9')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		output_bits = strtol(s,NULL,10);

		ptr += 11;
		ptr2 = s;
		while (ptr[0] != 10 && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		output_channels = strtol(s,NULL,10);

		new_song = TRUE;
		return TRUE;
		}
	return FALSE;
}

	/* we parse the output to get the current song position
		including seconds and frames (played and remaining) */
static void parse_frame_info()
{
	gchar frame_cnt[12] = "";
	gchar frame_cnt_rem [12] = "";
	gchar sec_cnt [12] = "";
	gchar sec_cnt_rem [12] = "";
	gchar min_cnt [12] = "";
	gchar min_cnt_rem [12] = "";
	gchar *ptr;
	gchar *ptr2;
	
	if (debug_mode) printf("f");

	ptr = pipebuf1;
	ptr += 6;
	ptr2 = frame_cnt;
	while (ptr[0] != '[' && ptr[0] != '\0')
		{
		if (ptr[0] != ' ')
			{
			ptr2[0] = ptr[0];
			ptr2++;
			}
		ptr++;
		}
	ptr2[0] = '\0';
	ptr++;
	ptr2 = frame_cnt_rem;
	while (ptr[0] != ']' && ptr[0] != '\0')
		{
		if (ptr[0] != ' ')
			{
			ptr2[0] = ptr[0];
			ptr2++;
			}
		ptr++;
		}
	ptr2[0] = '\0';
	while (ptr[0] != ':' && ptr[0] != '\0')
		{
		ptr++;
		}
	ptr++;

	if (mpg123_version == MPG123_VER_0_59O) /* 0.00 [000.00] */
		{
		ptr2 = sec_cnt;
		while (ptr[0] != '[' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		ptr++;
		ptr2 = sec_cnt_rem;
		while (ptr[0] != ']' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		}
	else /* 00:00.00 [00:00.00] */
		{
		ptr2 = min_cnt;
		while (ptr[0] != ':' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		ptr++;
		ptr2 = sec_cnt;
		while (ptr[0] != '[' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		ptr++;
		ptr2 = min_cnt_rem;
		while (ptr[0] != ':' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		ptr++;
		ptr2 = sec_cnt_rem;
		while (ptr[0] != ']' && ptr[0] != '\0')
			{
			if (ptr[0] != ' ')
				{
				ptr2[0] = ptr[0];
				ptr2++;
				}
			ptr++;
			}
		ptr2[0] = '\0';
		}

	frames = strtol(frame_cnt,NULL,10);
	frames_remaining = strtol(frame_cnt_rem,NULL,10);
	seconds = (strtol(min_cnt,NULL,10) * 60) + strtol(sec_cnt,NULL,10);
	seconds_remaining = (strtol(min_cnt_rem,NULL,10) * 60) + strtol(sec_cnt_rem,NULL,10);
	
/*	printf("min = [%s] sec = [%s] (%d)\n", min_cnt, sec_cnt, seconds);*/
}

static void handle_warnings()
{
	if (!strcmp(pipebuf1, "")) return;
	/* normal message output */
	if (!strncmp(pipebuf1, "High Perf", 9)) return;
	if (!strncmp(pipebuf1, "Uses code", 9)) return;
	if (!strncmp(pipebuf1, "THIS SOFT", 9)) return;
	if (!strncmp(pipebuf1, "Playing M", 9)) return;
	/* ID3 tags */
	if (!strncmp(pipebuf1, "Title  :", 8)) return;
	if (!strncmp(pipebuf1, "Album  :", 8)) return;
	if (!strncmp(pipebuf1, "Comment:", 8)) return;
	/* misc output */
	if (!strncmp(pipebuf1, "Junk at t", 9)) return;
	if (!strncmp(pipebuf1, "mpg123: Can't rewind stream", 27)) return;
	if (!strncmp(pipebuf1, "Directory:", 10)) return;

	/* for freeamp in mpg123 compat mode */
	if (!strncmp(pipebuf1, "Copyright", 9)) return;
	if (!strncmp(pipebuf1, "This soft", 9)) return;

	/* ignore lines that begin with an escape sequence (0.59r) */
	if (pipebuf1[0] == 27 && pipebuf1[1] == ']') return;

	printf("Warning, unrecognized mpg123 output:\"%s\")\n",pipebuf1);
	if (TRUE)
		{
		if (debug_mode)
			{
			gchar *ptr = pipebuf1;
			while (ptr[0] != '\0')
				{
				printf("[%d]", ptr[0]);
				ptr++;
				}
			putchar ('\n');
			}
		}

}


static void stop_data()
{
	if (signal_timeout_id)
		{
		gtk_timeout_remove(signal_timeout_id);
		signal_timeout_id = 0;
		}
	if (mpgpipe)
		{
		fclose (mpgpipe);
		mpgpipe = NULL;
		}
	pid = 0;
if (debug_mode) printf("closed\n");
}

static void song_finished()
{
	stop_data();
	module_playback_end();
}

/* this routine parses the output from mpg123 to check it's current status
   when the end is detected, 1 or TRUE is returned; any error return -1;
   a detected frame (second & frame info) return 1 or TRUE; 2 is returned
   on anything else (including header info) */
static int parse_pipe_buffer()
{
	/* check for Frame data */
	if (!strncmp(pipebuf1,"Frame",5))
		{
		parse_frame_info();
		return 1;
		}

	/* check for end of song (mpg123 reports a line starting
		with a '[') */
	if (!strncmp(pipebuf1,"[",1) &&
	    (!ran_freeamp || strncmp(pipebuf1, "[0:00] Decoding of", 18) != 0)) /* this line is for freeamp bug */
		{
		waitpid (pid, NULL, 0);
		song_finished();
		return 0;
		}

	/* we start looking for mpg123 errors here! */

	/* mpg123 errors */
	if (!strncmp(pipebuf1,"Can't open",10) ||
	    !strncmp(pipebuf1,"connect",7) ||
	    strstr(pipebuf1, "file or directory") ||
	    !strncmp(pipebuf1,"HTTP",4) )
		{
		printf("Error, mpg123 reported:\"%s\")\n",pipebuf1);
		warning_dialog("GQmpeg: mpg123 error", pipebuf1);

		stop_playing_file();
		module_playback_error();
		return -1;
		}

	if (!parse_header_info())
		{
		/* Unknown output, print it, it may be relevent */
		handle_warnings();
		}
	return 2;
}

static void handle_end_of_file()
{
	printf("mpg123 disappeared! (unexpected EOF)\n");
	stop_playing_file();
	module_playback_error();
}

	/* copied from the Unix programming FAQ */
static int check_pipe_for_data(int fd)
     {
	int rc;
	fd_set fds;
	struct timeval tv;
	
	FD_ZERO(&fds);
	FD_SET(fd,&fds);
	tv.tv_sec = tv.tv_usec = 0;

	rc = select(fd+1, &fds, NULL, NULL, &tv);
	if (rc < 0)
		return -1;

	return FD_ISSET(fd,&fds) ? 1 : 0;
}

static gint read_data(gpointer data)
{
	static int dcheck;
	int c, d;
	gint is_http = GPOINTER_TO_INT(data);

	if (debug_mode) printf("I");

	if (status != STATUS_PLAY) return FALSE;
	
	if (!mpgpipe)
		{
		mpgpipe = fdopen (mypipe[0], "r");

		if (debug_mode) printf("pipe opened\n");

		setvbuf(mpgpipe,NULL,_IONBF,BUFSIZ);
		pipebuf_ptr = pipebuf1;
		}

	/* if mpg123 starts sending too much data, we exit this loop
		after 8k bytes so the program will respond to user
		input. Otherwise, the program will appear hung.*/
	d = 8000;

	while (d > 0 && check_pipe_for_data(mypipe[0]) == 1)
		{
		c = getc(mpgpipe);

/* to debug mpg123's output
		if (TRUE) putchar(c)
*/

		if (c == -1)
			{
			handle_end_of_file();
			return FALSE;
			}

#ifdef 0
		/* we ignore escape sequences (for now) */
		if (c == 27 && getc(mpgpipe) == ']')
			{
			gint finished = FALSE;
			gint over_count = 8; /* don't go forever, may be a broken escape sequence */
			while (!finished && over_count > 0)
				{
				c = getc(mpgpipe);
				if (c == -1)
					{
					handle_end_of_file();
					return FALSE;
					}
				if (c == ';') finished = TRUE;
				over_count--;
				}
			c = getc(mpgpipe);
			if (c == -1)
				{
				handle_end_of_file();
				return FALSE;
				}
			}
#endif

		if (c == 13 || c == 10 || c== 7) /* the 7 is for 0.59r and it's escape sequences */
			{
			pipebuf_ptr[0] = '\0';
			if (parse_pipe_buffer() == -1) return FALSE;
			if (status == STATUS_NEXT) return FALSE;
			pipebuf_ptr = pipebuf1;
			}
		else if (pipebuf_ptr - pipebuf1 < 255)
			{
			/* um, we ignore extra data beyond 255 chars,
			   it is probably junk anyway, and may cause
			   an over-run problem */
			pipebuf_ptr[0] = c;
			pipebuf_ptr++;
			}
		d--;
		}

	/* here we check to see if mpg123 sent data within the last
		10 seconds (66 times through [at 150ms per] with no
		data). no data means mpg123 encountered an error we
		did not catch or it crashed
		NOTE: 20 seconds (132 times) for http. */
	if ( d == 8000)
		dcheck++;
	else
		dcheck = 0;
	if (dcheck > 66 && (!is_http || dcheck > 132))
		{
		printf("Error: mpg123 stopped sending data!\n");
		stop_playing_file();
		module_playback_error();
		return FALSE;
		}

	if (debug_mode) printf("O");

	return TRUE;
}

static gint start_playing_file(SongData *sd, gint position)
{
	pid_t frk_pid;
	char cmd_arguments[20][512];
	char *cmd_ptr[20];
	int cmd_cnt = 0;
	gint start_frame;
	gchar *exec_bin = "mpg123";

	ran_freeamp = FALSE;

	if (position == 0)
		{
		start_frame = 0;
		}
	else
		{
		if (seconds_remaining + seconds > 0 && frames_remaining + frames > 0)
			{
			start_frame = (gfloat)position / (seconds_remaining + seconds) * (frames_remaining + frames);
			}
		else
			{
			start_frame = 0;
			}
		}

	/* create all command line arguments */

	if (mpg123_force_freeamp || mpg123_use_freeamp)
		{
		ran_freeamp = TRUE;
		exec_bin = "freeamp";

		strcpy(cmd_arguments[cmd_cnt],"freeamp");
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;

		strcpy(cmd_arguments[cmd_cnt],"-ui");
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		}

	strcpy(cmd_arguments[cmd_cnt],"mpg123");
	cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
	cmd_cnt++;

	strcpy(cmd_arguments[cmd_cnt],"-v");
	cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
	cmd_cnt++;

	strcpy(cmd_arguments[cmd_cnt],"-k");
	cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
	cmd_cnt++;
	sprintf(cmd_arguments[cmd_cnt],"%d",start_frame);
	cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
	cmd_cnt++;

	if (mpg123_buffer_enable)
		{
		strcpy(cmd_arguments[cmd_cnt],"-b");
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		sprintf(cmd_arguments[cmd_cnt],"%d",mpg123_buffer);
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		}

	if (mpg123_downsample != MPG123_DOWNSAMPLE_AUTO)
		{
		if (mpg123_downsample == MPG123_DOWNSAMPLE_22)
			{
			strcpy(cmd_arguments[cmd_cnt],"-2");
			cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
			cmd_cnt++;
			}
		if (mpg123_downsample == MPG123_DOWNSAMPLE_11)
			{
			strcpy(cmd_arguments[cmd_cnt],"-4");
			cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
			cmd_cnt++;
			}
		if (mpg123_downsample == MPG123_DOWNSAMPLE_CUSTOM)
			{
			sprintf(cmd_arguments[cmd_cnt],"-r %d", mpg123_custom_sample_size);
			cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
			cmd_cnt++;
			}
		}

	if (mpg123_mono)
		{
		strcpy(cmd_arguments[cmd_cnt],"-m");
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		}

	if (mpg123_8bit)
		{
		strcpy(cmd_arguments[cmd_cnt],"--8bit");
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		}

	if (mpg123_aggressive)
		{
		strcpy(cmd_arguments[cmd_cnt],"--aggressive");
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		}

	if (mpg123_device_enable && mpg123_device)
		{
		strcpy(cmd_arguments[cmd_cnt],"-a");
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		strcpy(cmd_arguments[cmd_cnt],mpg123_device);
		cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
		cmd_cnt++;
		}

	strcpy(cmd_arguments[cmd_cnt], sd->path);
	cmd_ptr[cmd_cnt] = cmd_arguments[cmd_cnt];
	cmd_cnt++;

	strcpy(cmd_arguments[cmd_cnt], "");
	cmd_ptr[cmd_cnt] = NULL;
	cmd_cnt++;


	/* Create the pipe. */
	if (pipe (mypipe))
		{
		fprintf (stderr, "Pipe failed.\n");
		return FALSE;
		}

	if (debug_mode) printf("opening: %s\n", sd->path);

	/* Create the child process. */
	frk_pid = fork ();
	if (frk_pid == (pid_t) 0)
		{
		/* This is the child process. */
		dup2(mypipe[1],2);
		close(mypipe[0]);

		/* set the group (session) id to this process for future killing */
		setsid();

		execvp(exec_bin, cmd_ptr);
		printf("unable to run %s (in the path?)\n", exec_bin);
		_exit(1);
		}
	else if (frk_pid < (pid_t) 0)
		{
		/* The fork failed. */
		fprintf (stderr, "Fork failed.\n");
		pid = 0;
		return FALSE;
		}
	else
		{
		/* This is the parent process. */
		pid = (int) frk_pid;
		close(mypipe[1]);
		}

	if (debug_mode) printf("mpg123 pid = %d\n", pid);

	signal_timeout_id = gtk_timeout_add(150,(GtkFunction) read_data, GINT_TO_POINTER(type_is_http(sd->path)));

	return TRUE;
}

static void stop_playing_file()
{
	pid_t t;

	if (pid <= 0) return;

	if (debug_mode) printf("sending SIGTERM to pid = -%d\n", pid);

	/* kill the entire mpg123 group to work around mpg123 buffer bug */
	if (kill(-pid, SIGINT) ==-1)
		{
		/* eek!, maybe mpg123 is still in startup, so wait, try again */
		if (debug_mode) printf("waiting 1 sec to try again: pid = -%d\n", pid);
		sleep(1); /* is there a portable nanosleep like func? select? */
		if (kill(-pid, SIGINT) == -1)
			{
			printf("Failed to successfully send signal to pid = %d\n", (int)pid);
			}
		}

	if (debug_mode) printf("first waitpid = %d\n", (int)pid);
	t = waitpid (pid, NULL, WNOHANG);
	if (t != pid && t != -1)
		{
		if (debug_mode) printf("second waitpid, 1st returned: %d\n", t);
		waitpid (pid, NULL, 0);
		if (debug_mode) printf("second waitpid done.\n");
		}

	stop_data();
}

/*
 *----------------------------------------------------------------------------
 * mpg123 module callback funcs
 *----------------------------------------------------------------------------
 */

gint type_is_http(gchar *path)
{
	if (path[0] != 'h') return FALSE;
	if (strncmp(path, "http://", 7) == 0) return TRUE;

	return FALSE;
}

static void mpg123_data_init(SongData *sd)
{
	sd->type_description = "MPEG audio file";
}

static gint mpg123_data_set(SongData *sd, gint generic_info, gint format_info)
{
	if (type_is_http(sd->path))
		{
		if (generic_info)
			{
			sd->generic_info_loaded = TRUE;
			sd->title = g_strdup(sd->path + 7);
			}
		if (format_info)
			{
			sd->format_info_loaded = TRUE;
			}
		return TRUE; /* http has no extra info */
		}

	if (generic_info)
		{
		ID3_Data *idd;
		sd->generic_info_loaded = TRUE;

		idd = get_id3_tag(sd->path);
		if (idd)
			{
			sd->title = idd->title;
			sd->artist = idd->artist;
			sd->album = idd->album;
			sd->year = idd->year;
			sd->comment = idd->comment;
			sd->genre = idd->genre_description;

			g_free(idd); /* don't free members */
			}
		}
	if (format_info)
		{
		Mpg_Data *md;
		sd->format_info_loaded = TRUE;

		md = get_mpg_header_info(sd->path);
		if (md)
			{
			sd->length = md->length;
			sd->live = FALSE;
			sd->bit_rate = md->bit_rate;
			sd->bit_depth = 16;
			sd->khz_rate = md->sample_rate;
			if (md->stereo)
				{
				sd->channels = 2;
				}
			else
				{
				sd->channels = 1;
				}

			g_free(md);
			}
		}

	return TRUE;
}

/*
 *----------------------------------------------------------------------------
 * mpg123 module http edit funcs
 *----------------------------------------------------------------------------
 */

GtkWidget *mpg123_http_entry_setup(gchar *path)
{
	GtkWidget *vbox;
	GtkWidget *entry;

	vbox = gtk_vbox_new(FALSE, 0);

	entry = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(entry), "http://");
	gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
	gtk_widget_show(entry);

	gtk_object_set_user_data(GTK_OBJECT(vbox), entry);

	return vbox;
}

GtkWidget *mpg123_http_edit(SongData *sd)
{
	GtkWidget *vbox;
	GtkWidget *entry;

	vbox = gtk_vbox_new(FALSE, 0);

	entry = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(entry), sd->path);
	gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
	gtk_widget_show(entry);

	gtk_object_set_user_data(GTK_OBJECT(vbox), entry);

	return vbox;
}

gchar *mpg123_http_get_path(GtkWidget *widget)
{
	GtkWidget *entry = gtk_object_get_user_data(GTK_OBJECT(widget));

	if (!entry) return NULL;

	return g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
}

/*
 *----------------------------------------------------------------------------
 * mpg123 module play funcs
 *----------------------------------------------------------------------------
 */

static SongData *current_sd = NULL;

static gint mpg123_start(SongData *sd, gint position)
{
	if (debug_mode) printf("play started at %d\n", position);
	current_sd = sd;

	if (type_is_http(sd->path))
		{
		return start_playing_file(sd, 0);
		}
	else
		{
		return start_playing_file(sd, position);
		}
}

static gint mpg123_stop(SongData *sd)
{
	if (debug_mode) printf("play stopped\n");
	if (sd != current_sd)
		{
		printf("io_mpg123.c warning: attempt to stop playback of non matching file\n");
		}
	stop_playing_file();
	current_sd = NULL;
	return TRUE;
}

static gint mpg123_pause(SongData *sd)
{
	if (debug_mode) printf("play paused at %d\n", seconds);
	if (sd != current_sd)
		{
		printf("io_mpg123.c warning: attempt to pause playback of non matching file\n");
		}
	stop_playing_file();
	if (type_is_http(sd->path))
		{
		seconds = 0;
		frames = 0;
		}
	return TRUE;
}

static gint mpg123_continue(SongData *sd)
{
	if (debug_mode) printf("play restarted at %d\n", seconds);
	if (sd != current_sd)
		{
		printf("io_mpg123.c warning: attempt to continue playback of non matching file\n");
		}
	current_sd = sd;
	if (type_is_http(sd->path))
		{
		start_playing_file(sd, 0);
		}
	return start_playing_file(sd, seconds);
}

static gint mpg123_seek(SongData *sd, gint position)
{
	if (debug_mode) printf("play seeking to %d\n", position);
	if (sd != current_sd)
		{
		printf("io_mpg123.c warning: attempt to seek in matching file\n");
		}
	if (type_is_http(sd->path))
		{
		/* live streams don't seek */
		return TRUE;
		}

	seconds_remaining += seconds - position;
	seconds = position;

	if (status == STATUS_PLAY)
		{
		stop_playing_file();
		current_sd = sd;
		return start_playing_file(sd, position);
		}
	else
		{
		return TRUE;
		}
}

/*
 *----------------------------------------------------------------------------
 * player module interface routines
 *----------------------------------------------------------------------------
 */

void mpg123_init()
{
	IO_ModuleData *imd;
	gchar *mp3_desc = "MP3 file";
	gint id;

	if (!file_in_path("mpg123"))
		{
		printf("Failed to find mpg123 in your path!\n");
		if (file_in_path("freeamp"))
			{
			mpg123_use_freeamp = TRUE;
			mpg123_force_freeamp = TRUE;
			printf("But did find freeamp, will try to use it's mpg123 emulation.\n");
			}
		else
			{
			printf("mpg123 player module disabled, too bad this is currently the only module...\n");
			return;
			}
		}

	imd = g_new0(IO_ModuleData, 1);

	imd->title = "mpg123";
	imd->description = "mp3 player";

	imd->songdata_init_func = mpg123_data_init;
	imd->songdata_info_func = mpg123_data_set;

	imd->start_func = mpg123_start;
	imd->stop_func = mpg123_stop;
	imd->pause_func = mpg123_pause;
	imd->continue_func = mpg123_continue;
	imd->seek_func = mpg123_seek;

	imd->config_load_func = mpg123_config_load;
	imd->config_save_func = mpg123_config_save;
	imd->config_init_func = mpg123_config_init;
	imd->config_apply_func = mpg123_config_apply;
	imd->config_close_func = mpg123_config_close;

	imd->info_func = mpg_create_info_window;

	id = player_module_register(imd);

	module_register_file_suffix_type(".mp3", mp3_desc, id);
	module_register_file_suffix_type(".mp2", mp3_desc, id);
	module_register_file_suffix_type(".mpeg", mp3_desc, id);
	module_register_file_suffix_type(".mpg", mp3_desc, id);
	module_register_misc_type("MP3/Shoutcast stream", "http://server:port", id, TRUE,
			type_is_http, mpg123_http_entry_setup, mpg123_http_edit, mpg123_http_get_path);

}

