diff --git a/engine/client/cl_cam.c b/engine/client/cl_cam.c
index 99d873017..b35ee73c1 100644
--- a/engine/client/cl_cam.c
+++ b/engine/client/cl_cam.c
@@ -163,7 +163,7 @@ void Cam_Lock(int pnum, int playernum)
 	
 	Skin_FlushPlayers();
 
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 	{
 		memcpy(&cl.stats[pnum], cl.players[playernum].stats, sizeof(cl.stats[pnum]));
 	}
@@ -569,7 +569,7 @@ void Cam_FinishMove(int pnum, usercmd_t *cmd)
 	if (cls.state != ca_active)
 		return;
 
-	if (!cl.spectator && cls.demoplayback != DPB_MVD) // only in spectator mode
+	if (!cl.spectator && (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)) // only in spectator mode
 		return;
 
 	if (cmd->buttons & BUTTON_ATTACK) 
diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c
index d745c42ea..b42ceb6e8 100644
--- a/engine/client/cl_demo.c
+++ b/engine/client/cl_demo.c
@@ -31,13 +31,15 @@ void CL_PlayDemo(char *demoname);
 char lastdemoname[256];
 
 extern cvar_t qtvcl_forceversion1;
+extern cvar_t qtvcl_eztvextensions;
 
 unsigned char demobuffer[1024*16];
 int demobuffersize;
 int demopreparsedbytes;
 qboolean disablepreparse;
+qboolean endofdemo;
 
-#define BUFFERTIME 0.1
+#define BUFFERTIME 0.5
 /*
 ==============================================================================
 
@@ -161,7 +163,7 @@ int demo_preparsedemo(unsigned char *buffer, int bytes)
 	int ofs;
 	unsigned int length;
 #define dem_mask 7
-	if (cls.demoplayback != DPB_MVD)
+	if (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)
 		return bytes;	//no need if its not an mvd (this simplifies it a little)
 
 	while (bytes>2)
@@ -246,7 +248,7 @@ int readdemobytes(int *readpos, void *data, int len)
 	else if (i < 0)
 	{	//0 means no data available yet
 		printf("VFS_READ failed\n");
-//		CL_StopPlayback();
+		endofdemo = true;
 		return 0;
 	}
 
@@ -255,6 +257,7 @@ int readdemobytes(int *readpos, void *data, int len)
 	if (len > demobuffersize)
 	{
 		len = demobuffersize;
+		return 0;
 	}
 	memcpy(data, demobuffer+*readpos, len);
 	*readpos += len;
@@ -274,10 +277,14 @@ void demo_flushcache(void)
 {
 	demobuffersize = 0;
 	demopreparsedbytes = 0;
+
+	//no errors yet
+	disablepreparse = false;
 }
 
 void demo_resetcache(int bytes, void *data)
 {
+	endofdemo = false;
 	demo_flushcache();
 
 	demobuffersize = bytes;
@@ -373,6 +380,13 @@ qboolean CL_GetDemoMessage (void)
 	int demopos = 0;
 	int msglength;
 
+	if (endofdemo)
+	{
+		endofdemo = false;
+		CL_StopPlayback ();
+		return 0;
+	}
+
 #ifdef NQPROT
 	if (cls.demoplayback == DPB_NETQUAKE 
 #ifdef Q2CLIENT
@@ -451,6 +465,7 @@ qboolean CL_GetDemoMessage (void)
 		{
 			return 0;
 		}
+		demo_flushbytes(demopos);
 		net_message.cursize = msglength;
 	
 		return 1;
@@ -458,7 +473,7 @@ qboolean CL_GetDemoMessage (void)
 #endif
 readnext:
 	// read the time from the packet
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 	{
 		if (realtime < 0)
 		{
@@ -517,7 +532,7 @@ readnext:
 	else
 		realtime = demotime; // we're warping
 
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 	{
 		if (msecsadded || cls.netchan.incoming_sequence < 2)
 		{
@@ -633,7 +648,7 @@ readit:
 		}
 		net_message.cursize = msglength;
 
-		if (cls.demoplayback == DPB_MVD)
+		if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 		{
 			switch(cls_lasttype)
 			{
@@ -665,7 +680,7 @@ readit:
 		readdemobytes (&demopos, &i, 4);
 		cls.netchan.incoming_sequence = LittleLong(i);
 
-		if (cls.demoplayback == DPB_MVD)
+		if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 			cls.netchan.incoming_acknowledged = cls.netchan.incoming_sequence;
 		goto readnext;
 
@@ -1449,7 +1464,7 @@ void CL_PlayDemo(char *demoname)
 	TP_ExecTrigger ("f_demostart");
 }
 
-void CL_QTVPlay (vfsfile_t *newf)
+void CL_QTVPlay (vfsfile_t *newf, qboolean iseztv)
 {
 	CL_Disconnect_f ();
 
@@ -1457,7 +1472,11 @@ void CL_QTVPlay (vfsfile_t *newf)
 
 	demo_flushcache();	//just in case
 
-	cls.demoplayback = DPB_MVD;
+	if (iseztv)
+		cls.demoplayback = DPB_EZTV;
+	else
+		cls.demoplayback = DPB_MVD;
+
 	cls.findtrack = true;
 
 	cls.state = ca_demostart;
@@ -1466,7 +1485,8 @@ void CL_QTVPlay (vfsfile_t *newf)
 	realtime = -BUFFERTIME;
 	cl.gametime = -BUFFERTIME;
 	cl.gametimemark = realtime;
-	Con_Printf("Buffering for %i seconds\n", (int)-realtime);
+	if (realtime < -0.5)
+		Con_Printf("Buffering for %i seconds\n", (int)-realtime);
 
 	cls.netchan.last_received=realtime;
 
@@ -1474,6 +1494,18 @@ void CL_QTVPlay (vfsfile_t *newf)
 	TP_ExecTrigger ("f_demostart");
 }
 
+void CL_Demo_ClientCommand(char *commandtext)
+{
+	unsigned char b = 1;
+	unsigned short len = LittleShort(strlen(commandtext) + 4);
+#ifndef _MSC_VER
+#warning "this needs buffering safely"
+#endif
+	VFS_WRITE(cls.demofile, &len, sizeof(len));
+	VFS_WRITE(cls.demofile, &b, sizeof(b));
+	VFS_WRITE(cls.demofile, commandtext, strlen(commandtext)+1);
+}
+
 char qtvhostname[1024];
 char qtvrequestbuffer[4096];
 int qtvrequestsize;
@@ -1494,6 +1526,7 @@ void CL_QTVPoll (void)
 	int numviewers = 0;
 	qboolean init_numplayers = false;
 	qboolean init_numviewers = false;
+	qboolean iseztv = false;
 	char srchost[256];
 
 
@@ -1548,88 +1581,98 @@ void CL_QTVPoll (void)
 			*e = '\0';
 			colon = strchr(s, ':');
 			if (colon)
-			{
 				*colon++ = '\0';
-				if (!strcmp(s, "PERROR"))
-				{	//printable error
-					Con_Printf("QTV Error:\n%s\n", colon);
-				}
-				else if (!strcmp(s, "PRINT"))
-				{	//printable error
-					Con_Printf("QTV:\n%s\n", colon);
-				}
-				else if (!strcmp(s, "TERROR"))
-				{	//printable error
-					Con_Printf("QTV Error:\n%s\n", colon);
-				}
-				else if (!strcmp(s, "ADEMO"))
-				{	//printable error
-					Con_Printf("Demo%s is available\n", colon);
-				}
-
-				//generic sourcelist responce
-				else if (!strcmp(s, "ASOURCE"))
-				{	//printable source
-					if (!saidheader)
-					{
-						saidheader=true;
-						Con_Printf("Available Sources:\n");
-					}
-					Con_Printf("%s\n", colon);
-					//we're too lazy to even try and parse this
-				}
-
-				//v1.1 sourcelist responce includes SRCSRV, SRCHOST, SRCPLYRS, SRCVIEWS, SRCID
-				else if (!strcmp(s, "SRCSRV"))
-				{
-					//the proxy's source string (beware of file:blah without file:blah@blah)
-				}
-				else if (!strcmp(s, "SRCHOST"))
-				{
-					//the hostname from the server the stream came from
-					Q_strncpyz(srchost, colon, sizeof(srchost));
-				}
-				else if (!strcmp(s, "SRCPLYRS"))
-				{
-					//number of active players actually playing on that stream
-					numplayers = atoi(colon);
-					init_numplayers = true;
-				}
-				else if (!strcmp(s, "SRCVIEWS"))
-				{
-					//number of people watching this stream on the proxy itself
-					numviewers = atoi(colon);
-					init_numviewers = true;
-				}
-				else if (!strcmp(s, "SRCID"))
-				{
-					streamid = atoi(colon);
-
-					//now put it on a menu
-					if (!sourcesmenu)
-					{
-						m_state = m_complex;
-						key_dest = key_menu;
-						sourcesmenu = M_CreateMenu(0);
-
-						MC_AddPicture(sourcesmenu, 16, 4, "gfx/qplaque.lmp");
-						MC_AddCenterPicture(sourcesmenu, 4, "gfx/p_option.lmp");
-					}
-					if (init_numplayers == true && init_numviewers == true)
-						MC_AddConsoleCommand(sourcesmenu, 42, (sourcenum++)*8 + 32, va("%s (p%i, v%i)", srchost, numplayers, numviewers), va("qtvplay %i@%s\n", streamid, qtvhostname));
-					//else
-					//	FIXME: add error message here
-				}
-				//end of sourcelist entry
-
-				else if (!strcmp(s, "BEGIN"))
-					streamavailable = true;
-			}
 			else
-			{
-				if (!strcmp(s, "BEGIN"))
-					streamavailable = true;
+				colon = "";
+
+			if (!strcmp(s, "PERROR"))
+			{	//printable error
+				Con_Printf("QTV Error:\n%s\n", colon);
 			}
+			else if (!strcmp(s, "PRINT"))
+			{	//printable error
+				Con_Printf("QTV:\n%s\n", colon);
+			}
+			else if (!strcmp(s, "TERROR"))
+			{	//printable error
+				Con_Printf("QTV Error:\n%s\n", colon);
+			}
+			else if (!strcmp(s, "ADEMO"))
+			{	//printable error
+				Con_Printf("Demo%s is available\n", colon);
+			}
+
+			//generic sourcelist responce
+			else if (!strcmp(s, "ASOURCE"))
+			{	//printable source
+				if (!saidheader)
+				{
+					saidheader=true;
+					Con_Printf("Available Sources:\n");
+				}
+				Con_Printf("%s\n", colon);
+				//we're too lazy to even try and parse this
+			}
+
+			else if (!strcmp(s, "BEGIN"))
+			{
+				if (*colon)
+					Con_Printf("streaming \"%s\" from qtv\n", colon);
+				else
+					Con_Printf("qtv connection established\n", colon);
+				streamavailable = true;
+			}
+
+			//eztv extensions to v1.0
+			else if (!strcmp(s, "QTV_EZQUAKE_EXT"))
+			{
+				iseztv = true;
+				Con_Printf("Warning: eztv extensions %s\n", colon);
+			}
+
+			//v1.1 sourcelist response includes SRCSRV, SRCHOST, SRCPLYRS, SRCVIEWS, SRCID
+			else if (!strcmp(s, "SRCSRV"))
+			{
+				//the proxy's source string (beware of file:blah without file:blah@blah)
+			}
+			else if (!strcmp(s, "SRCHOST"))
+			{
+				//the hostname from the server the stream came from
+				Q_strncpyz(srchost, colon, sizeof(srchost));
+			}
+			else if (!strcmp(s, "SRCPLYRS"))
+			{
+				//number of active players actually playing on that stream
+				numplayers = atoi(colon);
+				init_numplayers = true;
+			}
+			else if (!strcmp(s, "SRCVIEWS"))
+			{
+				//number of people watching this stream on the proxy itself
+				numviewers = atoi(colon);
+				init_numviewers = true;
+			}
+			else if (!strcmp(s, "SRCID"))
+			{
+				streamid = atoi(colon);
+
+				//now put it on a menu
+				if (!sourcesmenu)
+				{
+					m_state = m_complex;
+					key_dest = key_menu;
+					sourcesmenu = M_CreateMenu(0);
+
+					MC_AddPicture(sourcesmenu, 16, 4, "gfx/qplaque.lmp");
+					MC_AddCenterPicture(sourcesmenu, 4, "gfx/p_option.lmp");
+				}
+				if (init_numplayers == true && init_numviewers == true)
+					MC_AddConsoleCommand(sourcesmenu, 42, (sourcenum++)*8 + 32, va("%s (p%i, v%i)", srchost, numplayers, numviewers), va("qtvplay %i@%s\n", streamid, qtvhostname));
+				//else
+				//	FIXME: add error message here
+			}
+			//end of sourcelist entry
+
 			//from e to s, we have a line	
 			s = e+1;
 		}
@@ -1638,7 +1681,7 @@ void CL_QTVPoll (void)
 
 	if (streamavailable)
 	{
-		CL_QTVPlay(qtvrequest);
+		CL_QTVPlay(qtvrequest, iseztv);
 		qtvrequest = NULL;
 		demo_resetcache(qtvrequestsize - (tail-qtvrequestbuffer), tail);
 		return;
@@ -1767,7 +1810,13 @@ void CL_QTVPlay_f (void)
 	}
 
 	VFS_WRITE(newf, connrequest, strlen(connrequest));
-	if (raw)
+
+	if (qtvcl_eztvextensions.value)
+	{
+		connrequest =	"QTV_EZQUAKE_EXT: 3\n";
+		VFS_WRITE(newf, connrequest, strlen(connrequest));
+	}
+	else if (raw)
 	{
 		connrequest =	"RAW: 1\n";
 		VFS_WRITE(newf, connrequest, strlen(connrequest));
@@ -1791,7 +1840,7 @@ void CL_QTVPlay_f (void)
 
 	if (raw)
 	{
-		CL_QTVPlay(newf);
+		CL_QTVPlay(newf, false);
 	}
 	else
 	{
diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c
index 054576ce2..400996294 100644
--- a/engine/client/cl_ents.c
+++ b/engine/client/cl_ents.c
@@ -458,7 +458,7 @@ void CL_ParsePacketEntities (qboolean delta)
 //		Con_Printf("%i %i from %i\n", cls.netchan.outgoing_sequence, cls.netchan.incoming_sequence, from);
 
 		oldpacket = cl.frames[newpacket].delta_sequence;
-		if (cls.demoplayback == DPB_MVD)
+		if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 			from = oldpacket = cls.netchan.incoming_sequence - 1;
 
 		if (cls.netchan.outgoing_sequence - cls.netchan.incoming_sequence >= UPDATE_BACKUP - 1) {
@@ -1548,7 +1548,7 @@ void CL_LinkPacketEntities (void)
 	CL_CalcClientTime();
 	servertime = cl.servertime;
 
-	pack = CL_ProcessPacketEntities(&servertime, !!cl_nolerp.value && cls.demoplayback != DPB_MVD);
+	pack = CL_ProcessPacketEntities(&servertime, !!cl_nolerp.value && (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV && cls.demoplayback != DPB_NETQUAKE));
 	if (!pack)
 		return;
 
@@ -2317,7 +2317,7 @@ void CL_ParsePlayerinfo (void)
 	oldstate = &cl.frames[oldparsecountmod].playerstate[num];
 	state = &cl.frames[parsecountmod].playerstate[num];
 
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 	{
 		player_state_t	*prevstate, dummy;
 		if (!cl.parsecount || info->prevcount > cl.parsecount || cl.parsecount - info->prevcount >= UPDATE_BACKUP - 1)
diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c
index d6937fc6c..c9dfe7880 100644
--- a/engine/client/cl_input.c
+++ b/engine/client/cl_input.c
@@ -902,7 +902,7 @@ void VARGS CL_SendClientCommand(qboolean reliable, char *format, ...)
 	char		string[2048];
 	clcmdbuf_t *buf, *prev;
 
-	if (cls.demoplayback)
+	if (cls.demoplayback && cls.demoplayback != DPB_EZTV)
 		return;	//no point.
 
 	va_start (argptr, format);
@@ -1218,12 +1218,13 @@ void CL_SendCmd (double frametime)
 	int clientcount;
 
 	extern cvar_t cl_maxfps;
+	clcmdbuf_t *next;
 
 	CL_ProxyMenuHooks();
 
 	if (cls.demoplayback != DPB_NONE)
 	{
-		if (cls.demoplayback == DPB_MVD)
+		if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 		{
 			extern cvar_t cl_splitscreen;
 			i = cls.netchan.outgoing_sequence & UPDATE_MASK;
@@ -1260,6 +1261,15 @@ void CL_SendCmd (double frametime)
 				Cam_FinishMove(plnum, cmd);
 			}
 
+			while (clientcmdlist)
+			{
+				next = clientcmdlist->next;
+				CL_Demo_ClientCommand(clientcmdlist->command);
+				Con_DPrintf("Sending stringcmd %s\n", clientcmdlist->command);
+				Z_Free(clientcmdlist);
+				clientcmdlist = next;
+			}
+
 			cls.netchan.outgoing_sequence++;
 		}
 
@@ -1270,25 +1280,24 @@ void CL_SendCmd (double frametime)
 	buf.cursize = 0;
 	buf.data = data;
 	CL_SendDownloadReq(&buf);
+
+
+	while (clientcmdlist)
 	{
-		clcmdbuf_t *next;
-		while (clientcmdlist)
+		next = clientcmdlist->next;
+		if (clientcmdlist->reliable)
 		{
-			next = clientcmdlist->next;
-			if (clientcmdlist->reliable)
-			{
-				MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
-				MSG_WriteString (&cls.netchan.message, clientcmdlist->command);
-			}
-			else
-			{
-				MSG_WriteByte (&buf, clc_stringcmd);
-				MSG_WriteString (&buf, clientcmdlist->command);
-			}
-			Con_DPrintf("Sending stringcmd %s\n", clientcmdlist->command);
-			Z_Free(clientcmdlist);
-			clientcmdlist = next;
+			MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
+			MSG_WriteString (&cls.netchan.message, clientcmdlist->command);
 		}
+		else
+		{
+			MSG_WriteByte (&buf, clc_stringcmd);
+			MSG_WriteString (&buf, clientcmdlist->command);
+		}
+		Con_DPrintf("Sending stringcmd %s\n", clientcmdlist->command);
+		Z_Free(clientcmdlist);
+		clientcmdlist = next;
 	}
 
 	if (msecs>150)	//q2 has 200 slop.
diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c
index 013a3eee3..25e5118f0 100644
--- a/engine/client/cl_main.c
+++ b/engine/client/cl_main.c
@@ -74,6 +74,7 @@ cvar_t	cl_solid_players = SCVAR("cl_solid_players", "1");
 cvar_t	cl_noblink = SCVAR("cl_noblink", "0");
 cvar_t	cl_servername = SCVAR("cl_servername", "none");
 cvar_t	qtvcl_forceversion1 = SCVAR("qtvcl_forceversion1", "0");
+cvar_t	qtvcl_eztvextensions = SCVAR("qtvcl_eztvextensions", "0");
 
 cvar_t cl_demospeed = FCVAR("cl_demospeed", "demo_setspeed", "1", 0);
 
@@ -2456,7 +2457,7 @@ void CL_ReadPackets (void)
 			continue;
 		}
 
-		if (net_message.cursize < 6 && cls.demoplayback != DPB_MVD) //MVDs don't have the whole sequence header thing going on
+		if (net_message.cursize < 6 && (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)) //MVDs don't have the whole sequence header thing going on
 		{
 			Con_TPrintf (TL_RUNTPACKET,NET_AdrToString(net_from));
 			continue;
@@ -2512,7 +2513,7 @@ void CL_ReadPackets (void)
 			break;
 			break;
 		case CP_QUAKEWORLD:
-			if (cls.demoplayback == DPB_MVD)
+			if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 			{
 				MSG_BeginReading();
 				cls.netchan.last_received = realtime;
@@ -2547,7 +2548,7 @@ void CL_ReadPackets (void)
 		return;
 	}
 
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 		MVD_Interpolate();
 }
 
@@ -2905,6 +2906,7 @@ void CL_Init (void)
 	Cvar_Register (&ruleset_allow_larger_models,		cl_controlgroup);
 
 	Cvar_Register (&qtvcl_forceversion1, cl_controlgroup);
+	Cvar_Register (&qtvcl_eztvextensions, cl_controlgroup);
 #ifdef WEBCLIENT
 	Cmd_AddCommand ("ftp", CL_FTP_f);
 #endif
diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c
index 9085e0eb1..ad0971e8d 100644
--- a/engine/client/cl_parse.c
+++ b/engine/client/cl_parse.c
@@ -872,10 +872,19 @@ void Sound_NextDownload (void)
 	else
 #endif
 	{
-		if (CL_RemoveClientCommands("modellist"))
-			Con_Printf("Multiple modellists\n");
-//		CL_SendClientCommand ("modellist %i 0", cl.servercount);
-		CL_SendClientCommand (true, modellist_name, cl.servercount, 0);
+		if (cls.demoplayback == DPB_EZTV)
+		{
+			if (CL_RemoveClientCommands("qtvmodellist"))
+				Con_Printf("Multiple modellists\n");
+			CL_SendClientCommand (true, "qtvmodellist %i 0", cl.servercount);
+		}
+		else
+		{
+			if (CL_RemoveClientCommands("modellist"))
+				Con_Printf("Multiple modellists\n");
+//			CL_SendClientCommand ("modellist %i 0", cl.servercount);
+			CL_SendClientCommand (true, modellist_name, cl.servercount, 0);
+		}
 	}
 }
 
@@ -910,6 +919,11 @@ void CL_RequestNextDownload (void)
 			return;	//not yet
 
 		cl.sendprespawn = false;
+#ifdef _MSC_VER
+		//FIXME: timedemo timer should start here.
+#else
+#warning timedemo timer should start here
+#endif
 
 
 #ifdef Q2CLIENT
@@ -926,11 +940,21 @@ void CL_RequestNextDownload (void)
 				Con_Printf("\n\n-------------\nCouldn't download %s - cannot fully connect\n", cl.worldmodel->name);
 				return;
 			}
+
+			if (cls.demoplayback == DPB_EZTV)
+			{
+				if (CL_RemoveClientCommands("qtvspawn"))
+					Con_Printf("Multiple prespawns\n");
+				CL_SendClientCommand(true, "qtvspawn %i 0 %i", cl.servercount, cl.worldmodel->checksum2);
+			}
+			else
+			{
 		// done with modellist, request first of static signon messages
-			if (CL_RemoveClientCommands("prespawn"))
-				Con_Printf("Multiple prespawns\n");
-	//		CL_SendClientCommand("prespawn %i 0 %i", cl.servercount, cl.worldmodel->checksum2);
-			CL_SendClientCommand(true, prespawn_name, cl.servercount, LittleLong(cl.worldmodel->checksum2));
+				if (CL_RemoveClientCommands("prespawn"))
+					Con_Printf("Multiple prespawns\n");
+	//			CL_SendClientCommand("prespawn %i 0 %i", cl.servercount, cl.worldmodel->checksum2);
+				CL_SendClientCommand(true, prespawn_name, cl.servercount, LittleLong(cl.worldmodel->checksum2));
+			}
 		}
 
 	}
@@ -1679,7 +1703,7 @@ void CL_ParseServerData (void)
 		T_FreeStrings();
 	}
 
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 	{
 		int i;
 		extern float nextdemotime;
@@ -1762,11 +1786,20 @@ void CL_ParseServerData (void)
 	else
 #endif
 	{
-		if (CL_RemoveClientCommands("soundlist"))
-			Con_Printf("Multiple soundlists\n");
-		// ask for the sound list next
-//		CL_SendClientCommand ("soundlist %i 0", cl.servercount);
-		CL_SendClientCommand (true, soundlist_name, cl.servercount, 0);
+		if (cls.demoplayback == DPB_EZTV)
+		{
+			if (CL_RemoveClientCommands("qtvsoundlist"))
+				Con_Printf("Multiple soundlists\n");
+			CL_SendClientCommand (true, "qtvsoundlist %i 0", cl.servercount);
+		}
+		else
+		{
+			if (CL_RemoveClientCommands("soundlist"))
+				Con_Printf("Multiple soundlists\n");
+			// ask for the sound list next
+//			CL_SendClientCommand ("soundlist %i 0", cl.servercount);
+			CL_SendClientCommand (true, soundlist_name, cl.servercount, 0);
+		}
 	}
 
 	// now waiting for downloads, etc
@@ -2273,10 +2306,13 @@ void CL_ParseSoundlist (void)
 
 	if (n)
 	{
-		if (CL_RemoveClientCommands("soundlist"))
-			Con_Printf("Multiple soundlists\n");
-//		CL_SendClientCommand("soundlist %i %i", cl.servercount, n);
-		CL_SendClientCommand(true, soundlist_name, cl.servercount, n);
+		if (cls.demoplayback != DPB_EZTV)
+		{
+			if (CL_RemoveClientCommands("soundlist"))
+				Con_Printf("Multiple soundlists\n");
+//			CL_SendClientCommand("soundlist %i %i", cl.servercount, n);
+			CL_SendClientCommand(true, soundlist_name, cl.servercount, n);
+		}
 		return;
 	}
 
@@ -2344,10 +2380,13 @@ void CL_ParseModellist (qboolean lots)
 
 	if (n)
 	{
-		if (CL_RemoveClientCommands("modellist"))
-			Con_Printf("Multiple modellists\n");
-//		CL_SendClientCommand("modellist %i %i", cl.servercount, n);
-		CL_SendClientCommand(true, modellist_name, cl.servercount, (nummodels&0xff00) + n);
+		if (cls.demoplayback != DPB_EZTV)
+		{
+			if (CL_RemoveClientCommands("modellist"))
+				Con_Printf("Multiple modellists\n");
+//			CL_SendClientCommand("modellist %i %i", cl.servercount, n);
+			CL_SendClientCommand(true, modellist_name, cl.servercount, (nummodels&0xff00) + n);
+		}
 		return;
 	}
 
@@ -2885,13 +2924,13 @@ void CL_ParseClientdata (void)
 	oldparsecountmod = parsecountmod;
 
 	i = cls.netchan.incoming_acknowledged;
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 		cl.oldparsecount = i - 1;
 	cl.parsecount = i;
 	i &= UPDATE_MASK;
 	parsecountmod = i;
 	frame = &cl.frames[i];
-	if (cls.demoplayback == DPB_MVD)
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 		frame->senttime = realtime - host_frametime;
 	parsecounttime = cl.frames[i].senttime;
 
@@ -3110,21 +3149,9 @@ void CL_ServerInfo (void)
 CL_SetStat
 =====================
 */
-void CL_SetStat (int pnum, int stat, int value)
+static void CL_SetStat_Internal (int pnum, int stat, int value)
 {
 	int	j;
-	if (stat < 0 || stat >= MAX_CL_STATS)
-		return;
-//		Host_EndGame ("CL_SetStat: %i is invalid", stat);
-
-	if (cls.demoplayback == DPB_MVD)
-	{
-		extern int cls_lastto;
-		cl.players[cls_lastto].stats[stat]=value;
-		if ( spec_track[pnum] != cls_lastto )
-			return;
-	}
-
 	if (cl.stats[pnum][stat] != value)
 		Sbar_Changed ();
 
@@ -3138,15 +3165,6 @@ void CL_SetStat (int pnum, int stat, int value)
 	if (stat == STAT_VIEWHEIGHT && cls.z_ext & Z_EXT_VIEWHEIGHT)
 		cl.viewheight[pnum] = value;
 
-	if (stat == STAT_TIME && (cls.fteprotocolextensions & PEXT_ACCURATETIMINGS))
-	{
-		cl.oldgametime = cl.gametime;
-		cl.oldgametimemark = cl.gametimemark;
-
-		cl.gametime = value * 0.001;
-		cl.gametimemark = realtime;
-	}
-
 	if (stat == STAT_WEAPON)
 	{
 		if (cl.stats[pnum][stat] != value)
@@ -3164,6 +3182,34 @@ void CL_SetStat (int pnum, int stat, int value)
 		TP_StatChanged(stat, value);
 }
 
+void CL_SetStat (int pnum, int stat, int value)
+{
+	if (stat < 0 || stat >= MAX_CL_STATS)
+		return;
+//		Host_EndGame ("CL_SetStat: %i is invalid", stat);
+
+	if (stat == STAT_TIME && (cls.fteprotocolextensions & PEXT_ACCURATETIMINGS))
+	{
+		cl.oldgametime = cl.gametime;
+		cl.oldgametimemark = cl.gametimemark;
+
+		cl.gametime = value * 0.001;
+		cl.gametimemark = realtime;
+	}
+
+	if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
+	{
+		extern int cls_lastto;
+		cl.players[cls_lastto].stats[stat]=value;
+
+		for (pnum = 0; pnum < cl.splitclients; pnum++)
+			if (spec_track[pnum] == cls_lastto)
+				CL_SetStat_Internal(pnum, stat, value);
+	}
+	else
+		CL_SetStat_Internal(pnum, stat, value);
+}
+
 /*
 ==============
 CL_MuzzleFlash
@@ -4073,7 +4119,9 @@ void CL_ParseServerMessage (void)
 			break;
 
 		case svc_disconnect:
-			if (cls.state == ca_connected)
+			if (cls.demoplayback == DPB_EZTV)	//eztv fails to detect the end of demos.
+				MSG_ReadString();
+			else if (cls.state == ca_connected)
 			{
 				Host_EndGame ("Server disconnected\n"
 					"Server version may not be compatible");
@@ -4144,7 +4192,7 @@ void CL_ParseServerMessage (void)
 			break;
 #endif
 		case svc_setangle:
-			if (cls.demoplayback == DPB_MVD)
+			if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
 			{
 				i = MSG_ReadByte();
 				if (i != spec_track[0] || !autocam[0])
diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c
index 8614095c3..85ed22249 100644
--- a/engine/client/cl_pred.c
+++ b/engine/client/cl_pred.c
@@ -379,6 +379,14 @@ void CL_PredictUsercmd (int pnum, player_state_t *from, player_state_t *to, user
 	VectorCopy (u->angles, pmove.angles);
 	VectorCopy (from->velocity, pmove.velocity);
 
+	if (!(pmove.velocity[0] == 0) && !(pmove.velocity[0] != 0))
+	{
+		Con_Printf("nan velocity!\n");
+		pmove.velocity[0] = 0;
+		pmove.velocity[1] = 0;
+		pmove.velocity[2] = 0;
+	}
+
 	pmove.jump_msec = (cls.z_ext & Z_EXT_PM_TYPE) ? 0 : from->jump_msec;
 	pmove.jump_held = from->jump_held;
 	pmove.waterjumptime = from->waterjumptime;
@@ -536,7 +544,7 @@ static void CL_LerpMove (int pnum, float msgtime)
 	int		i;
 	int		from, to;
 
-	if (cl_nolerp.value || cls.demoplayback == DPB_MVD)
+	if (cl_nolerp.value || cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV || cls.demoplayback == DPB_NETQUAKE)
 		return;
 
 	if (cls.netchan.outgoing_sequence < lastsequence) {
@@ -642,7 +650,7 @@ void CL_CalcClientTime(void)
 			cl.servertime = cl.oldgametime;
 	}
 
-	if (cls.protocol == CP_NETQUAKE || (cls.demoplayback && cls.demoplayback != DPB_MVD))
+	if (cls.protocol == CP_NETQUAKE || (cls.demoplayback && cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV))
 	{
 		float want;
 //		float off;
@@ -706,7 +714,7 @@ void CL_PredictMovePNum (int pnum)
 	if (cl_pushlatency.value > 0)
 		Cvar_Set (&cl_pushlatency, "0");
 
-	if (cl.paused && !cls.demoplayback!=DPB_MVD && (!cl.spectator || !autocam[pnum]))
+	if (cl.paused && !(cls.demoplayback!=DPB_MVD && cls.demoplayback!=DPB_EZTV) && (!cl.spectator || !autocam[pnum]))
 		return;
 
 	CL_CalcClientTime();
@@ -717,6 +725,11 @@ void CL_PredictMovePNum (int pnum)
 		return;
 	}
 
+	if (cls.demoplayback == DPB_NETQUAKE)
+	{
+		cl.ackedinputsequence = cls.netchan.outgoing_sequence;
+	}
+
 	if (!cl.ackedinputsequence)
 	{
 		return;
@@ -787,7 +800,7 @@ void CL_PredictMovePNum (int pnum)
 		}
 */	}
 #endif
-	if (((cl_nopred.value && cls.demoplayback!=DPB_MVD)|| cl.fixangle))
+	if (((cl_nopred.value && cls.demoplayback!=DPB_MVD && cls.demoplayback != DPB_EZTV)|| cl.fixangle))
 	{
 fixedorg:
 		VectorCopy (vel, cl.simvel[pnum]);
@@ -807,7 +820,7 @@ fixedorg:
 
 	to = &cl.frames[cl.ackedinputsequence & UPDATE_MASK];
 
-	if (Cam_TrackNum(pnum)>=0 && !cl_nolerp.value && cls.demoplayback != DPB_MVD)
+	if (Cam_TrackNum(pnum)>=0 && !cl_nolerp.value && cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV && cls.demoplayback != DPB_NETQUAKE)
 	{
 		float f;
 
diff --git a/engine/client/client.h b/engine/client/client.h
index 051a42922..ba97c7fe9 100644
--- a/engine/client/client.h
+++ b/engine/client/client.h
@@ -343,7 +343,7 @@ typedef struct
 // demo recording info must be here, because record is started before
 // entering a map (and clearing client_state_t)
 	qboolean	demorecording;
-	enum{DPB_NONE,DPB_QUAKEWORLD,DPB_MVD,
+	enum{DPB_NONE,DPB_QUAKEWORLD,DPB_MVD,DPB_EZTV,
 #ifdef NQPROT
 		DPB_NETQUAKE,
 #endif
diff --git a/engine/client/keys.c b/engine/client/keys.c
index 0dc818998..52044c9b2 100644
--- a/engine/client/keys.c
+++ b/engine/client/keys.c
@@ -1603,7 +1603,7 @@ void Key_Event (int key, qboolean down)
 // during demo playback, most keys bring up the main menu
 //
 
-	if (cls.demoplayback && cls.demoplayback != DPB_MVD && down && consolekeys[key] && key != K_TAB && key_dest == key_game)
+	if (cls.demoplayback && cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV && down && consolekeys[key] && key != K_TAB && key_dest == key_game)
 	{
 		M_ToggleMenu_f ();
 		return;