[SCM] vdr-plugin-streamdev packaging repository branch, upstream, updated. upstream/0.6.0-1-g199130d

etobi git at e-tobi.net
Wed Mar 6 21:52:06 UTC 2013


The following commit has been merged in the upstream branch:
commit 199130df7ad1cb9ad767438a7fadd07936473f49
Author: etobi <git at e-tobi.net>
Date:   Tue Mar 5 22:28:55 2013 +0100

    Imported Upstream version 0.6.0+git20130305

diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index c0e54e9..e4aad04 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -201,7 +201,16 @@ Ville Skytt
   for restricting VTP command RENR to liemikuutio patch < 1.32
   for fixing memory and filedescriptor leaks in libdvbmpeg
   for code cleanup and optimization
+  for correcting typos
 
 Methodus
   for suggesting to use HTTP code 503 for unavailable channels
 
+Uwe
+  for reporting a compiler error in client/device.c with VDR < 1.7.22
+
+Chris Tallon
+  for his kind permission to use VOMP's recplayer for replaying recordings
+
+macmenot
+  for adapting Makefiles to VDR 1.7.36+
diff --git a/HISTORY b/HISTORY
index 69c7643..64d17d7 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,6 +1,33 @@
 VDR Plugin 'streamdev' Revision History
 ---------------------------------------
  
+- Adapted Makefiles to VDR 1.7.36+ (thanks to macmenot). Old makefiles have
+  been renamed to Makefile-1.7.33.
+- API changes of VDR 1.7.38 (thanks to mal at vdr-developer)
+- Added simple recordings menu in HTTP server
+- Restructured menuHTTP classes
+- Added RSS format for HTTP menus
+- Recordings can now also be selected by struct stat "st_dev:st_ino.rec"
+- Implemented multi-device support for streamdev client (suggested by johns)
+- Basic support for HTTP streaming of recordings
+- Close writer when streamer is finished
+- Don't abort VTP connection if filter stream is broken
+- Restructured cStreamdevStreamer: Moved inbound buffer into actual subclass.
+- In cStreamdevStreamer dropped Activate(bool) and moved its code into Start().
+- Moved cStreamdevFilterStreamer to livefilter.[hc]
+- Return HTTP/1.1 compliant response headers plus some always useful headers
+- Return HTTP URL parameters ending with ".dlna.org" as response headers
+- Store HTTP URL parameters in a map
+- Support HTTP HEAD requests with external remuxer
+- Fixed always using priority 0 for HTTP HEAD requests
+- Start writer right after creating it
+- Corrected typos (thanks to Ville Skyttä)
+- Fixed compiler error in client/device.c with VDR < 1.7.22 (reported by
+  Uwe at vdrportal)
+- Updated Italian translation (thanks to Diego Pierotto)
+- Added DeviceName() and DeviceType() to client device. The server IP and the
+  number of the device used on the server are returned respectively.
+
 2012-05-29: Version 0.6.0
 
 - Reimplemented some client device methods
diff --git a/Makefile b/Makefile
index b375844..90f3261 100644
--- a/Makefile
+++ b/Makefile
@@ -1,19 +1,13 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile,v 1.23 2010/08/02 10:36:59 schmirl Exp $
+# $Id: $
 
-# The main source file name.
-#
-PLUGIN = streamdev
-
-### The C/C++ compiler and options:
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
 
-CC       ?= gcc
-CFLAGS   ?= -g -O2 -Wall
-
-CXX      ?= g++
-CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses
+PLUGIN = streamdev
 
 ### The version number of this plugin (taken from the main source file):
 
@@ -21,37 +15,30 @@ VERSION = $(shell grep 'const char \*VERSION *=' common.c | awk '{ print $$5 }'
 
 ### The directory environment:
 
-VDRDIR = ../../..
-LIBDIR = ../../lib
-TMPDIR = /tmp
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+#
+TMPDIR ?= /tmp
 
-### The version number of VDR (taken from VDR's "config.h"):
+### The compiler options:
 
-APIVERSION = $(shell grep 'define APIVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
-APIVERSNUM = $(shell grep 'define APIVERSNUM ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
-TSPLAYVERSNUM = $(shell grep 'define TSPLAY_PATCH_VERSION ' $(VDRDIR)/device.h | awk '{ print $$3 }')
+export CFLAGS   = $(call PKGCFG,cflags)
+export CXXFLAGS = $(call PKGCFG,cxxflags)
 
-### Allow user defined options to overwrite defaults:
+### The version number of VDR's plugin API:
 
-ifeq ($(shell test $(APIVERSNUM) -ge 10713; echo $$?),0)
-include $(VDRDIR)/Make.global
-else
-ifeq ($(shell test $(APIVERSNUM) -ge 10704 -o -n "$(TSPLAYVERSNUM)" ; echo $$?),0)
-DEFINES  += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
-CFLAGS   += -fPIC
-CXXFLAGS += -fPIC
-else
-CFLAGS   += -fPIC
-CXXFLAGS += -fPIC
-endif
-endif
+APIVERSION = $(call PKGCFG,apiversion)
 
--include $(VDRDIR)/Make.config
+### Allow user defined options to overwrite defaults:
 
-### export all vars for sub-makes, using absolute paths
+-include $(PLGCFG)
 
-VDRDIR := $(shell cd $(VDRDIR) >/dev/null 2>&1 && pwd)
+### export all vars for sub-makes, using absolute paths
 LIBDIR := $(shell cd $(LIBDIR) >/dev/null 2>&1 && pwd)
+LOCDIR := $(shell cd $(LOCDIR) >/dev/null 2>&1 && pwd)
 export
 unexport PLUGIN
 
@@ -63,6 +50,7 @@ PACKAGE = vdr-$(ARCHIVE)
 ### Includes and Defines (add further entries here):
 
 INCLUDES += -I$(VDRDIR)/include -I..
+export INCLUDES
 
 DEFINES += -D_GNU_SOURCE
 
@@ -75,7 +63,7 @@ endif
 
 ### The main target:
 
-.PHONY: all client server dist clean
+.PHONY: all client server install install-client install-server dist clean
 all: client server
 
 ### Targets:
@@ -83,20 +71,28 @@ all: client server
 client:
 	$(MAKE) -C ./tools
 	$(MAKE) -C ./client
-	# installs to $(LIBDIR)/libvdr-streamdev-client.so.$(APIVERSION)
 
 server:
 	$(MAKE) -C ./tools
 	$(MAKE) -C ./libdvbmpeg
 	$(MAKE) -C ./remux
 	$(MAKE) -C ./server
+
+install-client: client
+	$(MAKE) -C ./client install
+	# installs to $(LIBDIR)/libvdr-streamdev-client.so.$(APIVERSION)
+
+install-server: server
+	$(MAKE) -C ./server install
 	# installs to $(LIBDIR)/libvdr-streamdev-server.so.$(APIVERSION)
 
+install: install-client install-server
+
 dist: clean
 	@-rm -rf $(TMPDIR)/$(ARCHIVE)
 	@mkdir $(TMPDIR)/$(ARCHIVE)
 	@cp -a * $(TMPDIR)/$(ARCHIVE)
-	@tar czf $(PACKAGE).tgz --exclude CVS -C $(TMPDIR) $(ARCHIVE)
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
 	@-rm -rf $(TMPDIR)/$(ARCHIVE)
 	@echo Distribution package created as $(PACKAGE).tgz
 
diff --git a/Makefile b/Makefile-1.7.33
similarity index 100%
copy from Makefile
copy to Makefile-1.7.33
diff --git a/README b/README
index 8a5ede0..2e0165f 100644
--- a/README
+++ b/README
@@ -107,6 +107,11 @@ the streamdev-0.5.x releases for older versions.
 2.2 Compiling:
 --------------
 
+The Makefiles are for VDR 1.7.36 and above. For VDR 1.7.33 and below, please
+replace the Makefiles in the main directory and in the client/ and server/
+subdirectories with the corresponding Makefile-1.7.33 files. With VDR 1.7.34 and
+1.7.35 YMMV ;)
+
   cd vdr-1.X.X/PLUGINS/src
   tar xvfz vdr-streamdev-0.5.0.tgz
   ln -s streamdev-0.5.0 streamdev
@@ -296,7 +301,7 @@ For multicast streaming over the public Internet you would even need to register
 for your own IP range. So don't even think of multicasting via Internet with
 streamdev! Streamdev will send the stream only to one local ethernet segment and
 all clients must be connected to this same segment. There must not be a router
-inbetween. Also note that the client must not run on the streamdev-server
+in between. Also note that the client must not run on the streamdev-server
 machine.
 
 Each channel is offered on a different multicast IP. Channel 1 is available from
@@ -310,7 +315,7 @@ Binding an IGMP socket is a privileged operation, so you must start VDR as root.
 The multicast server is disabled by default. Enter the streamdev-server setup
 menu to enable it and - IMPORTANT - bind the multicast server to the IP of your
 VDR server's LAN ethernet card. The multicast server will refuse to start with
-the default bind adresse "0.0.0.0".
+the default bind address "0.0.0.0".
 
 Now edit your streamdevhosts.conf. To allow streaming of all channels, it must
 contain "239.255.0.0/16". Note that you cannot limit connections by client IP
@@ -365,9 +370,9 @@ setup parameter "Hide Mainmenu Entry" you can hide this menu item if you don't
 need it. "Suspend Server" is only useful if the server runs in "Offer suspend
 mode" with "Client may suspend" enabled.
 
-The parameter "Remote IP" uses an IP-Adress-Editor, where you can just enter
+The parameter "Remote IP" uses an IP-Address-Editor, where you can just enter
 the IP number with the number keys on your remote. After three digits (or if 
-the next digit would result in an invalid IP adress, or if the first digit is
+the next digit would result in an invalid IP address, or if the first digit is
 0), the current position jumps to the next one. You can change positions with 
 the left and right buttons, and you can cycle the current position using up 
 and down. To confirm the entered address, press OK. So, if you want to enter 
@@ -376,29 +381,26 @@ type "127001<OK>" on your remote. If you want to enter "192.168.1.12", type
 "1921681<Right>12<OK>". 
 
 The parameters "Remote IP" and "Remote Port" in the client's setup specify the 
-address of the remote VDR-to-VDR server to connect to. Activate the client by
-setting "Start Client" to yes. It is disabled by default, because it wouldn't
-make much sense to start the client without specifying a server anyway. The 
-client is activated after you push the OK button, so there's no need to restart
-VDR. Deactivation on-the-fly is not possible, so in order to deactivate the 
-client, you will have to restart VDR. However requests to switch channels will
-be refused by streamdev-client once it has been deactivated. All other settings
-can be changed without restarting VDR.
-
-The client will try to connect to the server (in case it isn't yet) whenever 
-a remote channel is requested. Just activate the client and switch to a 
-channel that's not available by local devices. If anything goes wrong with the 
-connection between the two, you will see it in the logfile instantly. If you 
-now switch the client to a channel which isn't covered by it's own local
-devices, it will ask the server for it. If the server can (currently) receive
-that channel, the client will show it until you switch again, or until the 
-server needs that card (if no other is free) for a recording on a different 
-transponder.
+address of the remote VDR-to-VDR server to connect to. The client is disabled
+by default, because it wouldn't make much sense to start the client without
+specifying a server anyway. Activate the client by setting "Simultaneously used
+Devices" to at least 1. Streamdev-client will allocate as many VDR devices as
+you configure here. Each of these devices opens one connection to the server
+and becomes associated with one of the server's devices (typically a DVB card)
+on demand.
 
 Only the needed PIDs are transferred, and additional PIDs can be turned on
 during an active transfer. This makes it possible to switch languages, receive
-additional channels (for recording on the client) and use plugins that use
-receivers themselves (like osdteletext).
+additional channels on the same transponder and use plugins that use receivers
+themselves (like osdteletext).
+
+So for viewing live TV a single device is sufficient. But if the client needs
+to receive channels from different transponders simultaneously (e.g. for PiP or
+client side recordings) a second device becomes necessary.
+
+To allocate additional devices, just increase the number and push the OK button.
+There's no need to restart VDR. Deleting VDR devices on-the-fly is not possible.
+However requests to switch channels will be refused by redundant devices.
 
 The default timeout of 2 seconds for network operations should be sufficient in
 most cases. Increase "Timeout" if you get frequent timeout errors in the log.
@@ -435,12 +437,9 @@ higher, live TV will use your DVB card until a recordings kicks in. Then the
 recording will take the DVB card and live TV will be shifted to streamdev
 (you'll notice a short interruption of live TV).
 
-Note that streamdev-client acts similar to a DVB card. It is possible to receive
-multiple channels simultaneously, but only from the same transponder. Just add
-additional instances of streamdev-client and you will be able to receive as many
-transponders at a time. The same trick allows a client to receive channels from
-different servers. To create an additional instance, copy the streamdev-client
-binary to a different name (e.g. streamdev-client2):
+To receive channels from multiple servers, create additional instances of the
+streamdev-client plugin. Simply copy (don't link!) the binary to a different
+name (e.g. streamdev-client2):
 
   cd VDRPLUGINLIBDIR
   cp libvdr-streamdev-client.so.1.X.X libvdr-streamdev-client2.so.1.X.X
diff --git a/client/Makefile b/client/Makefile
index ecc4787..ea9dc45 100644
--- a/client/Makefile
+++ b/client/Makefile
@@ -1,14 +1,18 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile,v 1.2 2010/07/19 13:49:25 schmirl Exp $
+# $Id: $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
 # By default the main source file also carries this name.
-#
+
 PLUGIN = streamdev-client
 
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
 ### Includes and Defines (add further entries here):
 
 DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
@@ -22,8 +26,7 @@ CLIENTOBJS = $(PLUGIN).o \
 	
 ### The main target:
 
-.PHONY: all i18n dist clean
-all: libvdr-$(PLUGIN).so i18n
+all: $(SOFILE) i18n
 
 ### Implicit rules:
 
@@ -34,51 +37,47 @@ all: libvdr-$(PLUGIN).so i18n
 
 MAKEDEP = $(CXX) -MM -MG
 DEPFILE = .dependencies
-
 $(DEPFILE): Makefile
-	@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
+	@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
 
 -include $(DEPFILE)
 
 ### Internationalization (I18N):
 
 PODIR     = po
-LOCALEDIR = $(VDRDIR)/locale
 I18Npo    = $(wildcard $(PODIR)/*.po)
-I18Nmsgs  = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Nmo    = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs  = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
 I18Npot   = $(PODIR)/$(PLUGIN).pot
 
 %.mo: %.po
 	msgfmt -c -o $@ $<
 
 $(I18Npot): $(CLIENTOBJS:%.o=%.c)
-	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<http://www.vdr-developer.org/mantisbt/>' -o $@ $^
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<vdrdev at schmirler.de>' -o $@ `ls $^`
 
 %.po: $(I18Npot)
-	msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+	msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
 	@touch $@
 
-$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
-	@mkdir -p $(dir $@)
-	cp $< $@
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
 
-i18n: $(I18Nmsgs)
+install-i18n: $(I18Nmsgs)
 
 ### Targets:
 
-libvdr-$(PLUGIN).so: $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a
+$(SOFILE): $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@
 
-%.so: 
-	$(CXX) $(CXXFLAGS) -shared $^ -o $@
-	@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
+install-lib: $(SOFILE)
+	install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
 
-dist: clean
-	@-rm -rf $(TMPDIR)/$(ARCHIVE)
-	@mkdir $(TMPDIR)/$(ARCHIVE)
-	@cp -a * $(TMPDIR)/$(ARCHIVE)
-	@tar czf $(PACKAGE).tgz --exclude CVS -C $(TMPDIR) $(ARCHIVE)
-	@-rm -rf $(TMPDIR)/$(ARCHIVE)
-	@echo Distribution package created as $(PACKAGE).tgz
+install: install-lib install-i18n
 
 clean:
-	@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~
+	@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+	@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(DEPFILE) *.so *.tgz core* *~
diff --git a/client/Makefile b/client/Makefile-1.7.33
similarity index 100%
copy from client/Makefile
copy to client/Makefile-1.7.33
diff --git a/client/device.c b/client/device.c
index 5c15a98..ef16690 100644
--- a/client/device.c
+++ b/client/device.c
@@ -29,30 +29,28 @@ using namespace std;
 
 #define VIDEOBUFSIZE MEGABYTE(3)
 
-cStreamdevDevice *cStreamdevDevice::m_Device = NULL;
 const cChannel *cStreamdevDevice::m_DenyChannel = NULL;
 
 cStreamdevDevice::cStreamdevDevice(void) {
-	m_Channel    = NULL;
-	m_TSBuffer   = NULL;
+	m_Disabled = false;
+	m_ClientSocket = new cClientSocket();
+	m_Channel = NULL;
+	m_TSBuffer = NULL;
 
-	m_Filters    = new cStreamdevFilters;
+	m_Filters = new cStreamdevFilters(m_ClientSocket);
 	StartSectionHandler();
 	isyslog("streamdev-client: got device number %d", CardIndex() + 1);
 
-	m_Device = this;
 	m_Pids = 0;
-	m_Priority = -100;
 }
 
 cStreamdevDevice::~cStreamdevDevice() {
 	Dprintf("Device gets destructed\n");
 
 	Lock();
-	m_Device = NULL;
 	m_Filters->SetConnection(-1);
-	ClientSocket.Quit();
-	ClientSocket.Reset();
+	m_ClientSocket->Quit();
+	m_ClientSocket->Reset();
 	Unlock();
 
 	Cancel(3);
@@ -60,6 +58,7 @@ cStreamdevDevice::~cStreamdevDevice() {
 	StopSectionHandler();
 	DELETENULL(m_Filters);
 	DELETENULL(m_TSBuffer);
+	delete m_ClientSocket;
 }
 
 #if APIVERSNUM >= 10700
@@ -84,7 +83,7 @@ bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel) const
 bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel)
 #endif
 {
-	return ClientSocket.DataSocket(siLive) != NULL &&
+	return m_ClientSocket->DataSocket(siLive) != NULL &&
 			m_Channel != NULL &&
 			Channel->Transponder() == m_Channel->Transponder();
 }
@@ -92,14 +91,14 @@ bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel)
 bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority, 
 		bool *NeedsDetachReceivers) const {
 #if APIVERSNUM >= 10725
-	bool prio = Priority == IDLEPRIORITY || Priority >= this->Priority();
+	bool prio = Priority == IDLEPRIORITY || Priority >= m_ClientSocket->Priority();
 #else
-	bool prio = Priority < 0 || Priority > this->Priority();
+	bool prio = Priority < 0 || Priority > m_ClientSocket->Priority();
 #endif
 	bool res = prio;
 	bool ndr = false;
 
-	if (!StreamdevClientSetup.StartClient || Channel == m_DenyChannel)
+	if (m_Disabled || Channel == m_DenyChannel)
 		return false;
 
 	Dprintf("ProvidesChannel, Channel=%s, Prio=%d\n", Channel->Name(), Priority);
@@ -117,7 +116,11 @@ bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
 			return false;
 	}
 
+#if APIVERSNUM >= 10722
 	if (IsTunedToTransponder(Channel)) {
+#else
+	if (const_cast<cStreamdevDevice*>(this)->IsTunedToTransponder(Channel)) {
+#endif
 		if (Channel->Ca() < CA_ENCRYPTED_MIN ||
 				(Channel->Vpid() && HasPid(Channel->Vpid())) ||
 				(Channel->Apid(0) && HasPid(Channel->Apid(0))))
@@ -127,7 +130,7 @@ bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
 	}
 	else if (prio) {
 		if (Priority == LIVEPRIORITY) {
-			if (ClientSocket.ServerVersion() >= 100) {
+			if (m_ClientSocket->ServerVersion() >= 100) {
 				Priority = StreamdevClientSetup.LivePriority;
 				UpdatePriority(true);
 			}
@@ -137,10 +140,10 @@ bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
 			}
 		}
 
-		res = ClientSocket.ProvidesChannel(Channel, Priority);
+		res = m_ClientSocket->ProvidesChannel(Channel, Priority);
 		ndr = Receiving();
 
-		if (ClientSocket.ServerVersion() >= 100)
+		if (m_ClientSocket->ServerVersion() >= 100)
 			UpdatePriority(false);
 	}
 
@@ -171,9 +174,9 @@ bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel,
 		m_Channel = Channel;
 		// Old servers delete cStreamdevLiveStreamer in ABRT.
 		// Delete it now or it will happen after we tuned to new channel
-		if (ClientSocket.ServerVersion() < 100)
+		if (m_ClientSocket->ServerVersion() < 100)
 			CloseDvr();
-		res = ClientSocket.SetChannelDevice(m_Channel);
+		res = m_ClientSocket->SetChannelDevice(m_Channel);
 	}
 	Dprintf("setchanneldevice res=%d\n", res);
 	return res;
@@ -185,7 +188,7 @@ bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) {
 
 	bool res = true; 
 	if (Handle->pid && (On || !Handle->used)) {
-		res = ClientSocket.SetPid(Handle->pid, On);
+		res = m_ClientSocket->SetPid(Handle->pid, On);
 
 		m_Pids += (!res) ? 0 : On ? 1 : -1;
 		if (m_Pids < 0) 
@@ -199,8 +202,8 @@ bool cStreamdevDevice::OpenDvr(void) {
 	LOCK_THREAD;
 
 	CloseDvr();
-	if (ClientSocket.CreateDataConnection(siLive)) {
-		m_TSBuffer = new cTSBuffer(*ClientSocket.DataSocket(siLive), MEGABYTE(2), CardIndex() + 1);
+	if (m_ClientSocket->CreateDataConnection(siLive)) {
+		m_TSBuffer = new cTSBuffer(*m_ClientSocket->DataSocket(siLive), MEGABYTE(2), CardIndex() + 1);
 	}
 	else {
 		esyslog("cStreamdevDevice::OpenDvr(): DVR connection FAILED");
@@ -213,26 +216,26 @@ void cStreamdevDevice::CloseDvr(void) {
 	Dprintf("CloseDvr\n");
 	LOCK_THREAD;
 
-	ClientSocket.CloseDvr();
+	m_ClientSocket->CloseDvr();
 	DELETENULL(m_TSBuffer);
 }
 
 bool cStreamdevDevice::GetTSPacket(uchar *&Data) {
-	if (m_TSBuffer && m_Device) {
+	if (m_TSBuffer) {
 		Data = m_TSBuffer->Get();
 #if 1 // TODO: this should be fixed in vdr cTSBuffer
 		// simple disconnect detection
 		static int m_TSFails = 0;
 		if (!Data) {
 			LOCK_THREAD;
-			if(!ClientSocket.DataSocket(siLive)) {
+			if(!m_ClientSocket->DataSocket(siLive)) {
 				return false; // triggers CloseDvr() + OpenDvr() in cDevice
                         }
-			cPoller Poller(*ClientSocket.DataSocket(siLive));
+			cPoller Poller(*m_ClientSocket->DataSocket(siLive));
 			errno = 0;
 			if (Poller.Poll() && !errno) {
 				char tmp[1];
-				if (recv(*ClientSocket.DataSocket(siLive), tmp, 1, MSG_PEEK) == 0 && !errno) {
+				if (recv(*m_ClientSocket->DataSocket(siLive), tmp, 1, MSG_PEEK) == 0 && !errno) {
 esyslog("cStreamDevice::GetTSPacket: GetChecked: NOTHING (%d)", m_TSFails);
 					m_TSFails++; 
 					if (m_TSFails > 10) {
@@ -260,75 +263,81 @@ int cStreamdevDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask) {
 		return -1;
 
 
-	if (!ClientSocket.DataSocket(siLiveFilter)) {
-		if (ClientSocket.CreateDataConnection(siLiveFilter)) {
-			m_Filters->SetConnection(*ClientSocket.DataSocket(siLiveFilter));
+	if (!m_ClientSocket->DataSocket(siLiveFilter)) {
+		if (m_ClientSocket->CreateDataConnection(siLiveFilter)) {
+			m_Filters->SetConnection(*m_ClientSocket->DataSocket(siLiveFilter));
 		} else {
 			isyslog("cStreamdevDevice::OpenFilter: connect failed: %m");
 			return -1;
 		}
 	}
 
-	if (ClientSocket.SetFilter(Pid, Tid, Mask, true))
+	if (m_ClientSocket->SetFilter(Pid, Tid, Mask, true))
 		return m_Filters->OpenFilter(Pid, Tid, Mask);
 
 	return -1;
 }
 
-bool cStreamdevDevice::Init(void) {
-	if (m_Device == NULL && StreamdevClientSetup.StartClient)
-		new cStreamdevDevice;
+bool cStreamdevDevice::ReInit(bool Disable) {
+	LOCK_THREAD;
+	m_Disabled = Disable;
+	m_Filters->SetConnection(-1);
+	m_Pids = 0;
+	m_ClientSocket->Quit();
+	m_ClientSocket->Reset();
+	//DELETENULL(m_TSBuffer);
 	return true;
 }
 
-bool cStreamdevDevice::ReInit(void) {
-	if(m_Device) {
-		m_Device->Lock();
-		m_Device->m_Filters->SetConnection(-1);
-		m_Device->m_Pids = 0;
-	}
-	ClientSocket.Quit();
-	ClientSocket.Reset();
-	if (m_Device != NULL) {
-		//DELETENULL(m_Device->m_TSBuffer);
-		m_Device->Unlock();
-	}
-	return StreamdevClientSetup.StartClient ? Init() : true;
-}
-
-void cStreamdevDevice::UpdatePriority(bool SwitchingChannels) {
-	if (m_Device) {
-		m_Device->Lock();
-		if (ClientSocket.SupportsPrio() && ClientSocket.DataSocket(siLive)) {
-			int Priority = m_Device->Priority();
+void cStreamdevDevice::UpdatePriority(bool SwitchingChannels) const {
+	if (!m_Disabled) {
+		//LOCK_THREAD;
+		const_cast<cStreamdevDevice*>(this)->Lock();
+		if (m_ClientSocket->SupportsPrio() && m_ClientSocket->DataSocket(siLive)) {
+			int Priority = this->Priority();
 			// override TRANSFERPRIORITY (-1) with live TV priority from setup
-			if (m_Device == cDevice::ActualDevice() && Priority == TRANSFERPRIORITY) {
-				Priority = StreamdevClientSetup.LivePriority;
+			if (this == cDevice::ActualDevice() && m_ClientSocket->Priority() == TRANSFERPRIORITY) {
+				int Priority = StreamdevClientSetup.LivePriority;
 				// temporarily lower priority
 				if (SwitchingChannels)
 					Priority--;
-				if (Priority < 0 && ClientSocket.ServerVersion() < 100)
+				if (Priority < 0 && m_ClientSocket->ServerVersion() < 100)
 					Priority = 0;
-
 			}
-			if (m_Device->m_Priority != Priority && ClientSocket.SetPriority(Priority))
-				m_Device->m_Priority = Priority;
+			m_ClientSocket->SetPriority(Priority);
 		}
-		m_Device->Unlock();
+		const_cast<cStreamdevDevice*>(this)->Unlock();
+	}
+}
+
+cString cStreamdevDevice::DeviceName(void) const {
+	return StreamdevClientSetup.RemoteIp;
+}
+
+cString cStreamdevDevice::DeviceType(void) const {
+	static int dev = -1;
+	static cString devType("STRDev");
+	int d = -1;
+	if (m_ClientSocket->DataSocket(siLive) != NULL)
+		m_ClientSocket->GetSignal(NULL, NULL, &d);
+	if (d != dev) {
+		dev = d;
+		devType = d < 0 ? "STRDev" : *cString::sprintf("STRD%2d", d);
 	}
+	return devType;
 }
 
 int cStreamdevDevice::SignalStrength(void) const {
 	int strength = -1;
-	if (ClientSocket.DataSocket(siLive) != NULL)
-		ClientSocket.GetSignal(&strength, NULL);
+	if (m_ClientSocket->DataSocket(siLive) != NULL)
+		m_ClientSocket->GetSignal(&strength, NULL, NULL);
 	return strength;
 }
 
 int cStreamdevDevice::SignalQuality(void) const {
 	int quality = -1;
-	if (ClientSocket.DataSocket(siLive) != NULL)
-		ClientSocket.GetSignal(NULL, &quality);
+	if (m_ClientSocket->DataSocket(siLive) != NULL)
+		m_ClientSocket->GetSignal(NULL, &quality, NULL);
 	return quality;
 }
 
diff --git a/client/device.c.orig b/client/device.c.orig
deleted file mode 100644
index 57f1b5b..0000000
--- a/client/device.c.orig
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- *  $Id: device.c,v 1.27 2010/08/18 10:26:55 schmirl Exp $
- */
- 
-#include "client/device.h"
-#include "client/setup.h"
-#include "client/filter.h"
-
-#include "tools/select.h"
-
-#include <vdr/config.h>
-#include <vdr/channels.h>
-#include <vdr/ringbuffer.h>
-#include <vdr/eit.h>
-#include <vdr/timers.h>
-
-#include <time.h>
-#include <iostream>
-
-using namespace std;
-
-#ifndef LIVEPRIORITY
-#define LIVEPRIORITY 0
-#endif
-
-#ifndef TRANSFERPRIORITY
-#define TRANSFERPRIORITY -1
-#endif
-
-#define VIDEOBUFSIZE MEGABYTE(3)
-
-cStreamdevDevice *cStreamdevDevice::m_Device = NULL;
-const cChannel *cStreamdevDevice::m_DenyChannel = NULL;
-
-cStreamdevDevice::cStreamdevDevice(void) {
-	m_Channel    = NULL;
-	m_TSBuffer   = NULL;
-
-	m_Filters    = new cStreamdevFilters;
-	StartSectionHandler();
-	isyslog("streamdev-client: got device number %d", CardIndex() + 1);
-
-	m_Device = this;
-	m_Pids = 0;
-	m_Priority = -100;
-}
-
-cStreamdevDevice::~cStreamdevDevice() {
-	Dprintf("Device gets destructed\n");
-
-	Lock();
-	m_Device = NULL;
-	m_Filters->SetConnection(-1);
-	ClientSocket.Quit();
-	ClientSocket.Reset();
-	Unlock();
-
-	Cancel(3);
-
-	StopSectionHandler();
-	DELETENULL(m_Filters);
-	DELETENULL(m_TSBuffer);
-}
-
-#if APIVERSNUM >= 10700
-int cStreamdevDevice::NumProvidedSystems(void) const
-{	return StreamdevClientSetup.NumProvidedSystems; }
-#endif
-
-bool cStreamdevDevice::ProvidesSource(int Source) const {
-	Dprintf("ProvidesSource, Source=%d\n", Source);
-	return true;
-}
-
-bool cStreamdevDevice::ProvidesTransponder(const cChannel *Channel) const
-{
-	Dprintf("ProvidesTransponder\n");
-	return true;
-}
-
-#if APIVERSNUM >= 10722
-bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel) const
-#else
-bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel)
-#endif
-{
-	bool res = false;
-	if (ClientSocket.DataSocket(siLive) != NULL
-			&& TRANSPONDER(Channel, m_Channel)
-			&& Channel->Ca() == CA_FTA
-			&& m_Channel->Ca() == CA_FTA)
-		res = true;
-	return res;
-}
-
-bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority, 
-		bool *NeedsDetachReceivers) const {
-	bool res = false;
-#if APIVERSNUM >= 10725
-	bool prio = Priority == IDLEPRIORITY || Priority >= this->Priority();
-#else
-	bool prio = Priority < 0 || Priority > this->Priority();
-#endif
-	bool ndr = false;
-
-	if (!StreamdevClientSetup.StartClient || Channel == m_DenyChannel)
-		return false;
-
-	Dprintf("ProvidesChannel, Channel=%s, Prio=%d\n", Channel->Name(), Priority);
-
-	if (StreamdevClientSetup.MinPriority <= StreamdevClientSetup.MaxPriority)
-	{
-		if (Priority < StreamdevClientSetup.MinPriority ||
-				Priority > StreamdevClientSetup.MaxPriority)
-			return false;
-	}
-	else
-	{
-		if (Priority < StreamdevClientSetup.MinPriority &&
-				Priority > StreamdevClientSetup.MaxPriority)
-			return false;
-	}
-
-	if (ClientSocket.DataSocket(siLive) != NULL 
-			&& TRANSPONDER(Channel, m_Channel))
-		res = true;
-	else {
-		if (Priority == LIVEPRIORITY)
-		{
-			if (ClientSocket.ServerVersion() >= 100)
-			{
-				Priority = StreamdevClientSetup.LivePriority;
-				UpdatePriority(true);
-			}
-			else
-			{
-				if (StreamdevClientSetup.LivePriority >= 0)
-					Priority = StreamdevClientSetup.LivePriority;
-			}
-		}
-
-		res = prio && ClientSocket.ProvidesChannel(Channel, Priority);
-
-		if (ClientSocket.ServerVersion() >= 100)
-			UpdatePriority(false);
-		ndr = true;
-	}
-
-	if (NeedsDetachReceivers)
-		*NeedsDetachReceivers = ndr;
-	Dprintf("prov res = %d, ndr = %d\n", res, ndr);
-	return res;
-}
-
-bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel, 
-		bool LiveView) {
-	Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(),
-			LiveView ? "true" : "false");
-	LOCK_THREAD;
-
-	m_UpdatePriority = ClientSocket.SupportsPrio();
-
-	if (LiveView)
-		return false;
-
-	if (ClientSocket.DataSocket(siLive) != NULL 
-			&& TRANSPONDER(Channel, m_Channel)
-			&& Channel->Ca() == CA_FTA
-			&& m_Channel->Ca() == CA_FTA)
-		return true;
-
-	DetachAllReceivers();
-	m_Channel = Channel;
-	bool r = ClientSocket.SetChannelDevice(m_Channel);
-	Dprintf("setchanneldevice r=%d\n", r);
-	return r;
-}
-
-bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) {
-	Dprintf("SetPid, Pid=%d, Type=%d, On=%d, used=%d\n", Handle->pid, Type, On, Handle->used);
-	LOCK_THREAD;
-
-	m_UpdatePriority = ClientSocket.SupportsPrio();
-
-	bool res = true; 
-	if (Handle->pid && (On || !Handle->used)) {
-		res = ClientSocket.SetPid(Handle->pid, On);
-
-		m_Pids += (!res) ? 0 : On ? 1 : -1;
-		if (m_Pids < 0) 
-			m_Pids = 0;
-	}
-	return res;
-}
-
-bool cStreamdevDevice::OpenDvr(void) {
-	Dprintf("OpenDvr\n");
-	LOCK_THREAD;
-
-	CloseDvr();
-	if (ClientSocket.CreateDataConnection(siLive)) {
-		m_TSBuffer = new cTSBuffer(*ClientSocket.DataSocket(siLive), MEGABYTE(2), CardIndex() + 1);
-	}
-	else {
-		esyslog("cStreamdevDevice::OpenDvr(): DVR connection FAILED");
-	}
-	return m_TSBuffer != NULL;
-}
-
-
-void cStreamdevDevice::CloseDvr(void) {
-	Dprintf("CloseDvr\n");
-	LOCK_THREAD;
-
-	ClientSocket.CloseDvr();
-	DELETENULL(m_TSBuffer);
-}
-
-bool cStreamdevDevice::GetTSPacket(uchar *&Data) {
-	if (m_TSBuffer && m_Device) {
-		Data = m_TSBuffer->Get();
-#if 1 // TODO: this should be fixed in vdr cTSBuffer
-		// simple disconnect detection
-		static int m_TSFails = 0;
-		if (!Data) {
-			LOCK_THREAD;
-			if(!ClientSocket.DataSocket(siLive)) {
-				return false; // triggers CloseDvr() + OpenDvr() in cDevice
-                        }
-			cPoller Poller(*ClientSocket.DataSocket(siLive));
-			errno = 0;
-			if (Poller.Poll() && !errno) {
-				char tmp[1];
-				if (recv(*ClientSocket.DataSocket(siLive), tmp, 1, MSG_PEEK) == 0 && !errno) {
-esyslog("cStreamDevice::GetTSPacket: GetChecked: NOTHING (%d)", m_TSFails);
-					m_TSFails++; 
-					if (m_TSFails > 10) {
-						isyslog("cStreamdevDevice::GetTSPacket(): disconnected");
-						m_Pids = 0;
-						CloseDvr();
-						m_TSFails = 0;
-						return false;
-					}
-					return true;
-				}
-			}
-			m_TSFails = 0;
-		}
-#endif
-		return true;
-	}
-	return false;
-}
-
-int cStreamdevDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask) {
-	Dprintf("OpenFilter\n");
-
-	if (!StreamdevClientSetup.StreamFilters)
-		return -1;
-
-
-	if (!ClientSocket.DataSocket(siLiveFilter)) {
-		if (ClientSocket.CreateDataConnection(siLiveFilter)) {
-			m_Filters->SetConnection(*ClientSocket.DataSocket(siLiveFilter));
-		} else {
-			isyslog("cStreamdevDevice::OpenFilter: connect failed: %m");
-			return -1;
-		}
-	}
-
-	if (ClientSocket.SetFilter(Pid, Tid, Mask, true))
-		return m_Filters->OpenFilter(Pid, Tid, Mask);
-
-	return -1;
-}
-
-bool cStreamdevDevice::Init(void) {
-	if (m_Device == NULL && StreamdevClientSetup.StartClient)
-		new cStreamdevDevice;
-	return true;
-}
-
-bool cStreamdevDevice::ReInit(void) {
-	if(m_Device) {
-		m_Device->Lock();
-		m_Device->m_Filters->SetConnection(-1);
-		m_Device->m_Pids = 0;
-	}
-	ClientSocket.Quit();
-	ClientSocket.Reset();
-	if (m_Device != NULL) {
-		//DELETENULL(m_Device->m_TSBuffer);
-		m_Device->Unlock();
-	}
-	return StreamdevClientSetup.StartClient ? Init() : true;
-}
-
-void cStreamdevDevice::UpdatePriority(bool SwitchingChannels) {
-	if (m_Device) {
-		m_Device->Lock();
-		if (m_Device->m_UpdatePriority && ClientSocket.DataSocket(siLive)) {
-			int Priority = m_Device->Priority();
-			// override TRANSFERPRIORITY (-1) with live TV priority from setup
-			if (m_Device == cDevice::ActualDevice() && Priority == TRANSFERPRIORITY) {
-				Priority = StreamdevClientSetup.LivePriority;
-				// temporarily lower priority
-				if (SwitchingChannels)
-					Priority--;
-				if (Priority < 0 && ClientSocket.ServerVersion() < 100)
-					Priority = 0;
-
-			}
-			if (m_Device->m_Priority != Priority && ClientSocket.SetPriority(Priority))
-				m_Device->m_Priority = Priority;
-		}
-		m_Device->Unlock();
-	}
-}
-
-int cStreamdevDevice::SignalStrength(void) const {
-	int strength = -1;
-	if (ClientSocket.DataSocket(siLive) != NULL)
-		ClientSocket.GetSignal(&strength, NULL);
-	return strength;
-}
-
-int cStreamdevDevice::SignalQuality(void) const {
-	int quality = -1;
-	if (ClientSocket.DataSocket(siLive) != NULL)
-		ClientSocket.GetSignal(NULL, &quality);
-	return quality;
-}
-
diff --git a/client/device.h b/client/device.h
index ff9092e..1edf956 100644
--- a/client/device.h
+++ b/client/device.h
@@ -17,18 +17,22 @@ class cTBString;
 class cStreamdevDevice: public cDevice {
 
 private:
+	bool                 m_Disabled;
+	cClientSocket       *m_ClientSocket;
 	const cChannel      *m_Channel;
 	cTSBuffer           *m_TSBuffer;
 	cStreamdevFilters   *m_Filters;
 	int                  m_Pids;
-	int                  m_Priority;
 
-	static cStreamdevDevice *m_Device;
 	static const cChannel   *m_DenyChannel;
 
 protected:
 	virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
-	virtual bool HasLock(int TimeoutMs) 
+#if APIVERSNUM >= 10738
+	virtual bool HasLock(int TimeoutMs) const
+#else
+	virtual bool HasLock(int TimeoutMs)
+#endif
 	{
 		//printf("HasLock is %d\n", (ClientSocket.DataSocket(siLive) != NULL));
 		//return ClientSocket.DataSocket(siLive) != NULL;
@@ -62,15 +66,15 @@ public:
 #else
 	virtual bool IsTunedToTransponder(const cChannel *Channel);
 #endif
+	virtual cString DeviceName(void) const;
+	virtual cString DeviceType(void) const;
 	virtual int SignalStrength(void) const;
 	virtual int SignalQuality(void) const;
 
-	static void UpdatePriority(bool SwitchingChannels = false);
+	bool ReInit(bool Disable);
+	void UpdatePriority(bool SwitchingChannels = false) const;
+	bool SuspendServer() { return m_ClientSocket->SuspendServer(); }
 	static void DenyChannel(const cChannel *Channel) { m_DenyChannel = Channel; }
-	static bool Init(void);
-	static bool ReInit(void);
-
-	static cStreamdevDevice *GetDevice(void) { return m_Device; }
 };
 
 #endif // VDR_STREAMDEV_DEVICE_H
diff --git a/client/filter.c b/client/filter.c
index c187e05..8606770 100644
--- a/client/filter.c
+++ b/client/filter.c
@@ -144,8 +144,9 @@ bool cStreamdevFilter::IsClosed(void) {
 
 // --- cStreamdevFilters -----------------------------------------------------
 
-cStreamdevFilters::cStreamdevFilters(void):
+cStreamdevFilters::cStreamdevFilters(cClientSocket *ClientSocket):
 		cThread("streamdev-client: sections assembler") {
+	m_ClientSocket = ClientSocket;
 	m_TSBuffer = NULL;
 }
 
@@ -173,7 +174,7 @@ void cStreamdevFilters::CarbageCollect(void) {
 			if (errno == ECONNREFUSED ||
 					errno == ECONNRESET ||
 					errno == EPIPE) {
-				ClientSocket.SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), false);
+				m_ClientSocket->SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), false);
 				Dprintf("cStreamdevFilters::CarbageCollector: filter closed: Pid %4d, Tid %3d, Mask %2x (%d filters left)",
 						(int)fi->Pid(), (int)fi->Tid(), fi->Mask(), Count()-1);
 
@@ -200,7 +201,7 @@ bool cStreamdevFilters::ReActivateFilters(void)
 	bool res = true;
 	CarbageCollect();
 	for (cStreamdevFilter *fi = First(); fi; fi = Next(fi)) {
-		res = ClientSocket.SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), true) && res;
+		res = m_ClientSocket->SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), true) && res;
 		Dprintf("ReActivateFilters(%d, %d, %d) -> %s", fi->Pid(), fi->Tid(), fi->Mask(), res ? "Ok" :"FAIL");
 	}
 	return res;
@@ -251,7 +252,7 @@ void cStreamdevFilters::Action(void) {
 						Dprintf("FATAL ERROR: %m\n");
 						esyslog("streamdev-client: couldn't send section packet: %m");
 					}
-					ClientSocket.SetFilter(f->Pid(), f->Tid(), f->Mask(), false);
+					m_ClientSocket->SetFilter(f->Pid(), f->Tid(), f->Mask(), false);
 					Del(f);
 					// Filter was closed.
 					//  - need to check remaining filters for another match
@@ -261,7 +262,7 @@ void cStreamdevFilters::Action(void) {
 		} else {
 #if 1 // TODO: this should be fixed in vdr cTSBuffer
 			// Check disconnection
-			int fd = *ClientSocket.DataSocket(siLiveFilter);
+			int fd = *m_ClientSocket->DataSocket(siLiveFilter);
 			if(fd < 0)
 				break;
 			cPoller Poller(fd);
@@ -273,7 +274,7 @@ void cStreamdevFilters::Action(void) {
 					++fails;
 					if (fails >= 10) {
 						esyslog("cStreamdevFilters::Action(): stream disconnected ?");
-						ClientSocket.CloseDataConnection(siLiveFilter);
+						m_ClientSocket->CloseDataConnection(siLiveFilter);
 						break;
 					}
 				} else {
diff --git a/client/filter.h b/client/filter.h
index 889006a..a096248 100644
--- a/client/filter.h
+++ b/client/filter.h
@@ -11,9 +11,11 @@
 
 class cTSBuffer;
 class cStreamdevFilter;
+class cClientSocket;
 
 class cStreamdevFilters: public cList<cStreamdevFilter>, public cThread {
 private:
+	cClientSocket     *m_ClientSocket;
 	cTSBuffer         *m_TSBuffer;
 	
 protected:
@@ -23,7 +25,7 @@ protected:
 	bool ReActivateFilters(void);
 
 public:
-	cStreamdevFilters(void);
+	cStreamdevFilters(cClientSocket *ClientSocket);
 	virtual ~cStreamdevFilters();
 
 	void SetConnection(int Handle);
diff --git a/client/po/de_DE.po b/client/po/de_DE.po
index 2b817ef..f347fe9 100644
--- a/client/po/de_DE.po
+++ b/client/po/de_DE.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
+"POT-Creation-Date: 2013-01-20 23:46+0100\n"
 "PO-Revision-Date: 2008-03-30 02:11+0200\n"
 "Last-Translator: Frank Schmirler <vdrdev at schmirler.de>\n"
 "Language-Team: German <vdr at linuxtv.org>\n"
@@ -31,8 +31,8 @@ msgstr "Konnte Server nicht pausieren!"
 msgid "Hide Mainmenu Entry"
 msgstr "Hauptmenüeintrag verstecken"
 
-msgid "Start Client"
-msgstr "Client starten"
+msgid "Simultaneously used Devices"
+msgstr "Gleichzeitig genutzte DVB-Karten"
 
 msgid "Remote IP"
 msgstr "IP der Gegenseite"
diff --git a/client/po/es_ES.po b/client/po/es_ES.po
index b512e0f..5182453 100644
--- a/client/po/es_ES.po
+++ b/client/po/es_ES.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
+"POT-Creation-Date: 2013-01-28 22:16+0100\n"
 "PO-Revision-Date: 2010-06-19 03:58+0100\n"
 "Last-Translator: Javier Bradineras <jbradi at hotmail.com>\n"
 "Language-Team: Spanish <vdr at linuxtv.org>\n"
@@ -31,8 +31,8 @@ msgstr "Imposible suspender el servidor!"
 msgid "Hide Mainmenu Entry"
 msgstr "Ocultar entrada en menú principal"
 
-msgid "Start Client"
-msgstr "Iniciar Cliente"
+msgid "Simultaneously used Devices"
+msgstr ""
 
 msgid "Remote IP"
 msgstr "Indicar IP del Servidor"
diff --git a/client/po/fi_FI.po b/client/po/fi_FI.po
index e356147..2416506 100644
--- a/client/po/fi_FI.po
+++ b/client/po/fi_FI.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
+"POT-Creation-Date: 2013-01-28 22:16+0100\n"
 "PO-Revision-Date: 2008-03-30 02:11+0200\n"
 "Last-Translator: Rolf Ahrenberg\n"
 "Language-Team: Finnish <vdr at linuxtv.org>\n"
@@ -31,8 +31,8 @@ msgstr "Palvelinta ei onnistuttu pysäyttämään!"
 msgid "Hide Mainmenu Entry"
 msgstr "Piilota valinta päävalikosta"
 
-msgid "Start Client"
-msgstr "Käynnistä VDR-asiakas"
+msgid "Simultaneously used Devices"
+msgstr ""
 
 msgid "Remote IP"
 msgstr "Etäkoneen IP-osoite"
diff --git a/client/po/fr_FR.po b/client/po/fr_FR.po
index 81e228b..7d938f0 100644
--- a/client/po/fr_FR.po
+++ b/client/po/fr_FR.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
+"POT-Creation-Date: 2013-01-28 22:16+0100\n"
 "PO-Revision-Date: 2008-03-30 02:11+0200\n"
 "Last-Translator: micky979 <micky979 at free.fr>\n"
 "Language-Team: French <vdr at linuxtv.org>\n"
@@ -31,8 +31,8 @@ msgstr "Impossible de suspendre le serveur!"
 msgid "Hide Mainmenu Entry"
 msgstr "Masquer dans le menu principal"
 
-msgid "Start Client"
-msgstr "Démarrage du client"
+msgid "Simultaneously used Devices"
+msgstr ""
 
 msgid "Remote IP"
 msgstr "Adresse IP du serveur"
diff --git a/client/po/it_IT.po b/client/po/it_IT.po
index 342c7ad..c311507 100644
--- a/client/po/it_IT.po
+++ b/client/po/it_IT.po
@@ -9,13 +9,13 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
-"PO-Revision-Date: 2010-06-19 03:58+0100\n"
+"POT-Creation-Date: 2013-01-28 22:16+0100\n"
+"PO-Revision-Date: 2012-06-10 20:34+0100\n"
 "Last-Translator: Diego Pierotto <vdr-italian at tiscali.it>\n"
 "Language-Team: Italian <vdr at linuxtv.org>\n"
 "Language: it\n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
 msgid "VTP Streaming Client"
@@ -33,8 +33,8 @@ msgstr "Impossibile sospendere il server!"
 msgid "Hide Mainmenu Entry"
 msgstr "Nascondi voce menu principale"
 
-msgid "Start Client"
-msgstr "Avvia Client"
+msgid "Simultaneously used Devices"
+msgstr ""
 
 msgid "Remote IP"
 msgstr "Indirizzo IP del Server"
@@ -43,19 +43,19 @@ msgid "Remote Port"
 msgstr "Porta Server Remoto"
 
 msgid "Timeout (s)"
-msgstr ""
+msgstr "Scadenza (s)"
 
 msgid "Filter Streaming"
 msgstr "Filtra trasmissione"
 
 msgid "Live TV Priority"
-msgstr ""
+msgstr "Priorità TV dal vivo"
 
 msgid "Minimum Priority"
-msgstr "Priorità minima"
+msgstr "Priorità minima"
 
 msgid "Maximum Priority"
-msgstr "Priorità massima"
+msgstr "Priorità massima"
 
 msgid "Broadcast Systems / Cost"
-msgstr ""
+msgstr "Costo / sistemi trasmissione"
diff --git a/client/po/lt_LT.po b/client/po/lt_LT.po
index a8f7412..409c267 100644
--- a/client/po/lt_LT.po
+++ b/client/po/lt_LT.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
+"POT-Creation-Date: 2013-01-28 22:16+0100\n"
 "PO-Revision-Date: 2009-11-26 21:57+0200\n"
 "Last-Translator: Valdemaras Pipiras <varas at ambernet.lt>\n"
 "Language-Team: Lithuanian <vdr at linuxtv.org>\n"
@@ -31,8 +31,8 @@ msgstr "Negali sustabdyti serverio!"
 msgid "Hide Mainmenu Entry"
 msgstr "Paslėpti pagrindinio meniu įrašą"
 
-msgid "Start Client"
-msgstr "Paleisti klientÄ…"
+msgid "Simultaneously used Devices"
+msgstr ""
 
 msgid "Remote IP"
 msgstr "Nuotolinis IP adresas"
diff --git a/client/po/ru_RU.po b/client/po/ru_RU.po
index c6528b1..8f5a9b2 100644
--- a/client/po/ru_RU.po
+++ b/client/po/ru_RU.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
+"POT-Creation-Date: 2013-01-28 22:16+0100\n"
 "PO-Revision-Date: 2008-06-26 15:36+0100\n"
 "Last-Translator: Oleg Roitburd <oleg at roitburd.de>\n"
 "Language-Team: Russian <vdr at linuxtv.org>\n"
@@ -31,8 +31,8 @@ msgstr "
 msgid "Hide Mainmenu Entry"
 msgstr "ÁßàïâÐâì Ò ÓÛÐÒÝÞÜ ÜÕÝî"
 
-msgid "Start Client"
-msgstr "ÁâÐàâ ÚÛØÕÝâÐ"
+msgid "Simultaneously used Devices"
+msgstr ""
 
 msgid "Remote IP"
 msgstr "ÃÔÐÛÕÝÝëÙ IP"
diff --git a/client/po/sk_SK.po b/client/po/sk_SK.po
index 54a9419..0037e27 100644
--- a/client/po/sk_SK.po
+++ b/client/po/sk_SK.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev_SK\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-03 23:57+0100\n"
+"POT-Creation-Date: 2013-01-28 22:16+0100\n"
 "PO-Revision-Date: \n"
 "Last-Translator: Milan Hrala <hrala.milan at gmail.com>\n"
 "Language-Team: Slovak <hrala.milan at gmail.com>\n"
@@ -33,8 +33,8 @@ msgstr "Nepodarilo sa pozastavi
 msgid "Hide Mainmenu Entry"
 msgstr "Schova» polo¾ku v hlavnom menu"
 
-msgid "Start Client"
-msgstr "Spusti» Klienta"
+msgid "Simultaneously used Devices"
+msgstr ""
 
 msgid "Remote IP"
 msgstr "Vzdialená IP"
diff --git a/client/setup.c b/client/setup.c
index 0fc9e9f..691ab35 100644
--- a/client/setup.c
+++ b/client/setup.c
@@ -5,7 +5,7 @@
 #include <vdr/menuitems.h>
 
 #include "client/setup.h"
-#include "client/device.h"
+#include "client/streamdev-client.h"
 
 #ifndef MINPRIORITY
 #define MINPRIORITY -MAXPRIORITY
@@ -50,11 +50,12 @@ bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) {
 	return true;
 }
 
-cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(void) {
+cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(cPluginStreamdevClient *Plugin) {
+	m_Plugin = Plugin;
 	m_NewSetup = StreamdevClientSetup;
 
 	Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry));
-	Add(new cMenuEditBoolItem(tr("Start Client"),        &m_NewSetup.StartClient));
+	Add(new cMenuEditIntItem (tr("Simultaneously used Devices"), &m_NewSetup.StartClient, 0, STREAMDEV_MAXDEVICES));
 	Add(new cMenuEditIpItem  (tr("Remote IP"),            m_NewSetup.RemoteIp));
 	Add(new cMenuEditIntItem (tr("Remote Port"),         &m_NewSetup.RemotePort, 0, 65535));
 	Add(new cMenuEditIntItem (tr("Timeout (s)"),         &m_NewSetup.Timeout, 1, 15));
@@ -74,11 +75,6 @@ cStreamdevClientMenuSetupPage::~cStreamdevClientMenuSetupPage() {
 }
 
 void cStreamdevClientMenuSetupPage::Store(void) {
-	if (m_NewSetup.StartClient != StreamdevClientSetup.StartClient) {
-		if (m_NewSetup.StartClient)
-			cStreamdevDevice::Init();
-	}
-
 	SetupStore("StartClient", m_NewSetup.StartClient);
 	if (strcmp(m_NewSetup.RemoteIp, "") == 0)
 		SetupStore("RemoteIp", "-none-");
@@ -97,6 +93,6 @@ void cStreamdevClientMenuSetupPage::Store(void) {
 
 	StreamdevClientSetup = m_NewSetup;
 
-	cStreamdevDevice::ReInit();
+	m_Plugin->Initialize();
 }
 
diff --git a/client/setup.h b/client/setup.h
index 7bcf889..5401271 100644
--- a/client/setup.h
+++ b/client/setup.h
@@ -28,15 +28,18 @@ struct cStreamdevClientSetup {
 
 extern cStreamdevClientSetup StreamdevClientSetup;
 
+class cPluginStreamdevClient;
+
 class cStreamdevClientMenuSetupPage: public cMenuSetupPage {
 private:
-	cStreamdevClientSetup m_NewSetup;
+	cPluginStreamdevClient *m_Plugin;
+	cStreamdevClientSetup    m_NewSetup;
 	
 protected:
 	virtual void Store(void);
 
 public:
-	cStreamdevClientMenuSetupPage(void);
+	cStreamdevClientMenuSetupPage(cPluginStreamdevClient *Plugin);
 	virtual ~cStreamdevClientMenuSetupPage();
 };
 
diff --git a/client/socket.c b/client/socket.c
index 4b58625..c0a6d1f 100644
--- a/client/socket.c
+++ b/client/socket.c
@@ -18,17 +18,17 @@
 #include "client/socket.h"
 #include "common.h"
 
-cClientSocket ClientSocket;
-
 cClientSocket::cClientSocket(void) 
 {
 	memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count);
 	m_ServerVersion = 0;
+	m_Priority = -100;
 	m_Prio = false;
 	m_Abort = false;
 	m_LastSignalUpdate = 0;
 	m_LastSignalStrength = -1;
 	m_LastSignalQuality = -1;
+	m_LastDev = -1;
 	Reset();
 }
 
@@ -283,15 +283,22 @@ bool cClientSocket::SetChannelDevice(const cChannel *Channel) {
 }
 
 bool cClientSocket::SetPriority(int Priority) {
+	if (Priority == m_Priority)
+		return true;
+
 	if (!CheckConnection()) return false;
 
 	CMD_LOCK;
 
 	std::string command = (std::string)"PRIO " + (const char*)itoa(Priority);
-	return Command(command, 220);
+	if (!Command(command, 220))
+		return false;
+
+	m_Priority = Priority;
+	return true;
 }
 
-bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality) {
+bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality, int *Dev) {
 	if (!CheckConnection()) return -1;
 
 	CMD_LOCK;
@@ -301,7 +308,8 @@ bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality) {
 		std::string buffer;
 		std::string command("SGNL");
 		if (!Send(command) || !Receive(command, &code, &buffer) || code != 220
-				|| sscanf(buffer.c_str(), "%*d %*d %d:%d", &m_LastSignalStrength, &m_LastSignalQuality) != 2) {
+				|| sscanf(buffer.c_str(), "%*d %d %d:%d", &m_LastDev, &m_LastSignalStrength, &m_LastSignalQuality) != 3) {
+			m_LastDev = -1;
 			m_LastSignalStrength = -1;
 			m_LastSignalQuality = -1;
 		}
@@ -311,6 +319,8 @@ bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality) {
 		*SignalStrength = m_LastSignalStrength;
 	if (SignalQuality)
 		*SignalQuality = m_LastSignalQuality;
+	if (Dev)
+		*Dev = m_LastDev;
 	return 0;
 }
 
diff --git a/client/socket.h b/client/socket.h
index a4d4e35..69876ce 100644
--- a/client/socket.h
+++ b/client/socket.h
@@ -23,11 +23,13 @@ private:
 	char          m_Buffer[BUFSIZ + 1]; // various uses
 	unsigned int  m_ServerVersion;
 	bool          m_Prio; // server supports command PRIO
+	int           m_Priority; // current device priority
 	bool          m_Abort; // quit command pending
 
 	time_t        m_LastSignalUpdate;
 	int           m_LastSignalStrength;
 	int           m_LastSignalQuality;
+	int           m_LastDev;
 protected:
 	/* Send Command, and return true if the command results in Expected. 
 	   Returns false on failure. */
@@ -54,10 +56,11 @@ public:
 	bool SetChannelDevice(const cChannel *Channel);
 	bool SupportsPrio() { return m_Prio; }
 	unsigned int ServerVersion() { return m_ServerVersion; }
+	int Priority() const { return m_Priority; }
 	bool SetPriority(int Priority);
 	bool SetPid(int Pid, bool On);
 	bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On);
-	bool GetSignal(int *SignalStrength, int *SignalQuality);
+	bool GetSignal(int *SignalStrength, int *SignalQuality, int *Dev);
 	bool CloseDvr(void);
 	bool SuspendServer(void);
 	bool Quit(void);
@@ -65,6 +68,4 @@ public:
 	cTBSocket *DataSocket(eSocketId Id) const;
 };
 
-extern class cClientSocket ClientSocket;
-
 #endif // VDR_STREAMDEV_CLIENT_CONNECTION_H
diff --git a/client/streamdev-client.c b/client/streamdev-client.c
index 088837f..4036eca 100644
--- a/client/streamdev-client.c
+++ b/client/streamdev-client.c
@@ -16,7 +16,7 @@
 
 const char *cPluginStreamdevClient::DESCRIPTION = trNOOP("VTP Streaming Client");
 
-cPluginStreamdevClient::cPluginStreamdevClient(void) {
+cPluginStreamdevClient::cPluginStreamdevClient(void): m_Devices() {
 }
 
 cPluginStreamdevClient::~cPluginStreamdevClient() {
@@ -26,9 +26,13 @@ const char *cPluginStreamdevClient::Description(void) {
 	return tr(DESCRIPTION);
 }
 
-bool cPluginStreamdevClient::Start(void) {
-	I18nRegister(PLUGIN_NAME_I18N);
-	cStreamdevDevice::Init();
+bool cPluginStreamdevClient::Initialize(void) {
+	for (int i = 0; i < STREAMDEV_MAXDEVICES; i++) {
+		if (m_Devices[i])
+			m_Devices[i]->ReInit(i >= StreamdevClientSetup.StartClient);
+		else if (i < StreamdevClientSetup.StartClient)
+			m_Devices[i] = new cStreamdevDevice();
+	}
 	return true;
 }
 
@@ -37,7 +41,7 @@ const char *cPluginStreamdevClient::MainMenuEntry(void) {
 }
 
 cOsdObject *cPluginStreamdevClient::MainMenuAction(void) {
-	if (ClientSocket.SuspendServer())
+	if (StreamdevClientSetup.StartClient && m_Devices[0]->SuspendServer())
 		Skins.Message(mtInfo, tr("Server is suspended"));
 	else
 		Skins.Message(mtError, tr("Couldn't suspend Server!"));
@@ -45,7 +49,7 @@ cOsdObject *cPluginStreamdevClient::MainMenuAction(void) {
 }
 
 cMenuSetupPage *cPluginStreamdevClient::SetupMenu(void) {
-  return new cStreamdevClientMenuSetupPage;
+  return new cStreamdevClientMenuSetupPage(this);
 }
 
 bool cPluginStreamdevClient::SetupParse(const char *Name, const char *Value) {
@@ -61,7 +65,8 @@ bool cPluginStreamdevClient::Service(const char *Id, void *Data) {
 }
 
 void cPluginStreamdevClient::MainThreadHook(void) {
-  cStreamdevDevice::UpdatePriority();
+	for (int i = 0; i < StreamdevClientSetup.StartClient; i++)
+		m_Devices[i]->UpdatePriority();
 }
 
 VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this!
diff --git a/client/streamdev-client.h b/client/streamdev-client.h
index 77d8af9..65b3582 100644
--- a/client/streamdev-client.h
+++ b/client/streamdev-client.h
@@ -9,16 +9,20 @@
 
 #include <vdr/plugin.h>
 
+#define STREAMDEV_MAXDEVICES 8
+class cStreamdevDevice;
+
 class cPluginStreamdevClient : public cPlugin {
 private:
-	static const char *DESCRIPTION;
+  static const char *DESCRIPTION;
+  cStreamdevDevice *m_Devices[STREAMDEV_MAXDEVICES];
 
 public:
   cPluginStreamdevClient(void);
   virtual ~cPluginStreamdevClient();
   virtual const char *Version(void) { return VERSION; }
   virtual const char *Description(void);
-  virtual bool Start(void);
+  virtual bool Initialize(void);
   virtual const char *MainMenuEntry(void);
   virtual cOsdObject *MainMenuAction(void);
   virtual cMenuSetupPage *SetupMenu(void);
diff --git a/common.c b/common.c
index 19efdb3..4ac7c6f 100644
--- a/common.c
+++ b/common.c
@@ -10,7 +10,7 @@
 
 using namespace std;
 
-const char *VERSION = "0.6.0";
+const char *VERSION = "0.6.0-git";
 
 const char cMenuEditIpItem::IpCharacters[] = "0123456789.";
 
diff --git a/remux/extern.c b/remux/extern.c
index 49be1ce..3ac68bb 100644
--- a/remux/extern.c
+++ b/remux/extern.c
@@ -295,7 +295,7 @@ void cTSExt::Put(const uchar *Data, int Count)
 }
 
 cExternRemux::cExternRemux(const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids):
-		m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)),
+		m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE)),
 		m_Remux(new cTSExt(m_ResultBuffer, Connection, Channel, Apids, Dpids))
 {
 	m_ResultBuffer->SetTimeouts(500, 100);
diff --git a/server/Makefile b/server/Makefile
index ba09649..37cd4bb 100644
--- a/server/Makefile
+++ b/server/Makefile
@@ -1,14 +1,18 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile,v 1.2 2010/07/19 13:49:31 schmirl Exp $
+# $Id: $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
 # By default the main source file also carries this name.
-#
+
 PLUGIN = streamdev-server
 
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
 ### Includes and Defines (add further entries here):
 
 DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
@@ -22,13 +26,12 @@ SERVEROBJS = $(PLUGIN).o \
 	componentVTP.o connectionVTP.o \
 	componentHTTP.o connectionHTTP.o menuHTTP.o \
 	componentIGMP.o connectionIGMP.o \
-	streamer.o livestreamer.o livefilter.o recplayer.o \
+	streamer.o livestreamer.o livefilter.o recstreamer.o recplayer.o \
 	menu.o suspend.o setup.o
 	
 ### The main target:
 
-.PHONY: all i18n clean
-all: libvdr-$(PLUGIN).so i18n
+all: $(SOFILE) i18n
 
 ### Implicit rules:
 
@@ -39,44 +42,48 @@ all: libvdr-$(PLUGIN).so i18n
 
 MAKEDEP = $(CXX) -MM -MG
 DEPFILE = .dependencies
-
 $(DEPFILE): Makefile
-	@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
+	@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
 
 -include $(DEPFILE)
 
 ### Internationalization (I18N):
 
 PODIR     = po
-LOCALEDIR = $(VDRDIR)/locale
 I18Npo    = $(wildcard $(PODIR)/*.po)
-I18Nmsgs  = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Nmo    = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs  = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
 I18Npot   = $(PODIR)/$(PLUGIN).pot
 
 %.mo: %.po
 	msgfmt -c -o $@ $<
 
 $(I18Npot): $(SERVEROBJS:%.o=%.c)
-	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<http://www.vdr-developer.org/mantisbt/>' -o $@ $^
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<vdrdev at schmirler.de>' -o $@ `ls $^`
 
 %.po: $(I18Npot)
-	msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+	msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
 	@touch $@
 
-$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
-	@mkdir -p $(dir $@)
-	cp $< $@
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
 
-i18n: $(I18Nmsgs)
+install-i18n: $(I18Nmsgs)
 
 ### Targets:
 
-libvdr-$(PLUGIN).so: $(SERVEROBJS) $(COMMONOBJS) \
+$(SOFILE): $(SERVEROBJS) $(COMMONOBJS) \
 	../tools/sockettools.a ../remux/remux.a ../libdvbmpeg/libdvbmpegtools.a
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@
+
+install-lib: $(SOFILE)
+	install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
 
-%.so: 
-	$(CXX) $(CXXFLAGS) -shared $^ -o $@
-	@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
+install: install-lib install-i18n
 
 clean:
-	@-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~
+	@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+	@-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~
diff --git a/server/Makefile b/server/Makefile-1.7.33
similarity index 96%
copy from server/Makefile
copy to server/Makefile-1.7.33
index ba09649..00c471c 100644
--- a/server/Makefile
+++ b/server/Makefile-1.7.33
@@ -22,7 +22,7 @@ SERVEROBJS = $(PLUGIN).o \
 	componentVTP.o connectionVTP.o \
 	componentHTTP.o connectionHTTP.o menuHTTP.o \
 	componentIGMP.o connectionIGMP.o \
-	streamer.o livestreamer.o livefilter.o recplayer.o \
+	streamer.o livestreamer.o livefilter.o recstreamer.o recplayer.o \
 	menu.o suspend.o setup.o
 	
 ### The main target:
diff --git a/server/connection.h b/server/connection.h
index 0d51da8..ba1f7bb 100644
--- a/server/connection.h
+++ b/server/connection.h
@@ -9,6 +9,7 @@
 #include "common.h"
 
 #include <map>
+#include <string>
 
 typedef std::map<std::string,std::string> tStrStrMap;
 typedef std::pair<std::string,std::string> tStrStr;
@@ -101,7 +102,7 @@ public:
 	/* Close the socket */
 	virtual bool Close(void);
 
-	/* Check if a device would be available for transfering the given
+	/* Check if a device would be available for transferring the given
 	   channel. This call has no side effects. */
 	static cDevice *CheckDevice(const cChannel *Channel, int Priority, bool LiveView, const cDevice *AvoidDevice = NULL);
 
diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c
index 63eb22e..ec10824 100644
--- a/server/connectionHTTP.c
+++ b/server/connectionHTTP.c
@@ -3,6 +3,15 @@
  */
 
 #include <ctype.h>
+#include <time.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <vdr/thread.h>
+#include <vdr/recording.h>
  
 #include "server/connectionHTTP.h"
 #include "server/menuHTTP.h"
@@ -12,10 +21,11 @@
 cConnectionHTTP::cConnectionHTTP(void): 
 		cServerConnection("HTTP"),
 		m_Status(hsRequest),
-		m_LiveStreamer(NULL),
-		m_Channel(NULL),
+		m_Streamer(NULL),
 		m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
-		m_ChannelList(NULL)
+		m_Channel(NULL),
+		m_Recording(NULL),
+		m_MenuList(NULL)
 {
 	Dprintf("constructor hsRequest\n");
 	m_Apid[0] = m_Apid[1] = 0;
@@ -24,7 +34,8 @@ cConnectionHTTP::cConnectionHTTP(void):
 
 cConnectionHTTP::~cConnectionHTTP() 
 {
-	delete m_LiveStreamer;
+	delete m_Streamer;
+	delete m_Recording;
 }
 
 bool cConnectionHTTP::CanAuthenticate(void)
@@ -48,9 +59,24 @@ bool cConnectionHTTP::Command(char *Cmd)
 					*v = 0;
 					SetHeader("REQUEST_METHOD", Cmd);
 					q = strchr(p, '?');
-					if (q)
+					if (q) {
 						*q = 0;
-					SetHeader("QUERY_STRING", q ? ++q : "");
+						SetHeader("QUERY_STRING", q + 1);
+						while (q++) {
+							char *n = strchr(q, '&');
+							if (n)
+								*n = 0;
+							char *e = strchr(q, '=');
+							if (e)
+								*e++ = 0;
+							else
+								e = n ? n : v;
+							m_Params.insert(tStrStr(q, e));
+							q = n;
+						}
+					}
+					else
+						SetHeader("QUERY_STRING", "");
 					SetHeader("PATH_INFO", p);
 					m_Status = hsHeaders;
 					return true;
@@ -133,10 +159,7 @@ bool cConnectionHTTP::ProcessRequest(void)
 		}
 		if (!authOk) {
 			isyslog("streamdev-server: HTTP authorization required");
-			DeferClose();
-			return Respond("HTTP/1.0 401 Authorization Required")
-				&& Respond("WWW-authenticate: basic Realm=\"Streamdev-Server\")")
-				&& Respond("");
+			return HttpResponse(401, true, NULL, "WWW-authenticate: basic Realm=\"Streamdev-Server\"");
 		}
 	}
 
@@ -146,84 +169,193 @@ bool cConnectionHTTP::ProcessRequest(void)
 		// should never happen
 		esyslog("streamdev-server connectionHTTP: Missing method or pathinfo");
 	} else if (it_method->second.compare("GET") == 0 && ProcessURI(it_pathinfo->second)) {
-		if (m_ChannelList)
-			return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
+		if (m_MenuList)
+			return Respond("%s", true, m_MenuList->HttpHeader().c_str());
 		else if (m_Channel != NULL) {
 			cDevice *device = NULL;
 			if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority))
 				device = GetDevice(m_Channel, StreamdevServerSetup.HTTPPriority);
 			if (device != NULL) {
 				device->SwitchChannel(m_Channel, false);
-				m_LiveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this);
-				if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) {
-					m_LiveStreamer->SetDevice(device);
+				cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this);
+				m_Streamer = liveStreamer;
+				if (liveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) {
+					liveStreamer->SetDevice(device);
 					if (!SetDSCP())
 						LOG_ERROR_STR("unable to set DSCP sockopt");
 					if (m_StreamType == stEXT) {
 						return Respond("HTTP/1.0 200 OK");
 					} else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) {
-						return Respond("HTTP/1.0 200 OK")
-						    && Respond("Content-Type: audio/mpeg")
-						    && Respond("icy-name: %s", true, m_Channel->Name())
-						    && Respond("");
+						return HttpResponse(200, false, "audio/mpeg", "icy-name: %s", m_Channel->Name());
 					} else if (ISRADIO(m_Channel)) {
-						return Respond("HTTP/1.0 200 OK")
-						    && Respond("Content-Type: audio/mpeg")
-						    && Respond("");
+						return HttpResponse(200, false, "audio/mpeg");
 					} else {
-						return Respond("HTTP/1.0 200 OK")
-						    && Respond("Content-Type: video/mpeg")
-						    && Respond("");
+						return HttpResponse(200, false, "video/mpeg");
 					}
 				}
-				DELETENULL(m_LiveStreamer);
+				DELETENULL(m_Streamer);
 			}
-			DeferClose();
-			return Respond("HTTP/1.0 503 Service unavailable")
-				&& Respond("");
+			return HttpResponse(503, true);
+		}
+		else if (m_Recording != NULL) {
+			Dprintf("GET recording\n");
+			cStreamdevRecStreamer* recStreamer = new cStreamdevRecStreamer(m_Recording, this);
+			m_Streamer = recStreamer;
+			int64_t from, to;
+			uint64_t total = recStreamer->GetLength();
+			if (ParseRange(from, to)) {
+				int64_t length = recStreamer->SetRange(from, to);
+				if (length < 0L)
+					return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
+				else
+					return HttpResponse(206, false, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length);
+			}
+			else
+				return HttpResponse(200, false, "video/mpeg", "Accept-Ranges: bytes");
 		}
 		else {
-			DeferClose();
-			return Respond("HTTP/1.0 404 not found")
-				&& Respond("");
+			return HttpResponse(404, true);
 		}
 	} else if (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) {
-		DeferClose();
-		if (m_ChannelList)
-			return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
+		if (m_MenuList) {
+			DeferClose();
+			return Respond("%s", true, m_MenuList->HttpHeader().c_str());
+		}
 		else if (m_Channel != NULL) {
-			if (ProvidesChannel(m_Channel, 0)) {
+			if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) {
 				if (m_StreamType == stEXT) {
-					// TODO
-					return Respond("HTTP/1.0 200 OK")
-					    && Respond("");
+					cStreamdevLiveStreamer *liveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this);
+					liveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
+					m_Streamer = liveStreamer;
+					return Respond("HTTP/1.0 200 OK");
 				} else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) {
-					return Respond("HTTP/1.0 200 OK")
-					    && Respond("Content-Type: audio/mpeg")
-					    && Respond("icy-name: %s", true, m_Channel->Name())
-					    && Respond("");
+					return HttpResponse(200, true, "audio/mpeg", "icy-name: %s", m_Channel->Name());
 				} else if (ISRADIO(m_Channel)) {
-					return Respond("HTTP/1.0 200 OK")
-					    && Respond("Content-Type: audio/mpeg")
-					    && Respond("");
+					return HttpResponse(200, true, "audio/mpeg");
 				} else {
-					return Respond("HTTP/1.0 200 OK")
-					    && Respond("Content-Type: video/mpeg")
-					    && Respond("");
+					return HttpResponse(200, true, "video/mpeg");
 				}
 			}
-			return Respond("HTTP/1.0 503 Service unavailable")
-				&& Respond("");
+			return HttpResponse(503, true);
+		}
+		else if (m_Recording != NULL) {
+			Dprintf("HEAD recording\n");
+			cStreamdevRecStreamer *recStreamer = new cStreamdevRecStreamer(m_Recording, this);
+			m_Streamer = recStreamer;
+			int64_t from, to;
+			uint64_t total = recStreamer->GetLength();
+			if (ParseRange(from, to)) {
+				int64_t length = recStreamer->SetRange(from, to);
+				if (length < 0L)
+					return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
+				else
+					return HttpResponse(206, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length);
+			}
+			else
+				return HttpResponse(200, true, "video/mpeg", "Accept-Ranges: bytes");
 		}
 		else {
-			return Respond("HTTP/1.0 404 not found")
-				&& Respond("");
+			return HttpResponse(404, true);
 		}
 	}
 
-	DeferClose();
-	return Respond("HTTP/1.0 400 Bad Request")
-		&& Respond("");
+	return HttpResponse(400, true);
+}
+
+static const char *AAA[] = {
+	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+static const char *MMM[] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+bool cConnectionHTTP::HttpResponse(int Code, bool Last, const char* ContentType, const char* Headers, ...)
+{
+        va_list ap;
+        va_start(ap, Headers);
+#if APIVERSNUM >= 10728
+        cString headers = cString::vsprintf(Headers, ap);
+#else
+        cString headers = cString::sprintf(Headers, ap);
+#endif
+        va_end(ap);
+
+	bool rc;
+	if (Last)
+		DeferClose();
+	switch (Code)
+	{
+		case 200: rc = Respond("HTTP/1.1 200 OK"); break;
+		case 206: rc = Respond("HTTP/1.1 206 Partial Content"); break;
+		case 400: rc = Respond("HTTP/1.1 400 Bad Request"); break;
+		case 401: rc = Respond("HTTP/1.1 401 Authorization Required"); break;
+		case 404: rc = Respond("HTTP/1.1 404 Not Found"); break;
+		case 416: rc = Respond("HTTP/1.1 416 Requested range not satisfiable"); break;
+		case 503: rc = Respond("HTTP/1.1 503 Service Unavailable"); break;
+		default:  rc = Respond("HTTP/1.1 500 Internal Server Error");
+	}
+	if (rc && ContentType)
+		rc = Respond("Content-Type: %s", true, ContentType);
+	
+	if (rc)
+		rc = Respond("Connection: close")
+			&& Respond("Pragma: no-cache")
+			&& Respond("Cache-Control: no-cache");
+
+	time_t t = time(NULL);
+	struct tm *gmt = gmtime(&t);
+	if (rc && gmt) {
+		char buf[] = "Date: AAA, DD MMM YYYY HH:MM:SS GMT";
+		if (snprintf(buf, sizeof(buf), "Date: %s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", AAA[gmt->tm_wday], gmt->tm_mday, MMM[gmt->tm_mon], gmt->tm_year + 1900, gmt->tm_hour, gmt->tm_min, gmt->tm_sec) == sizeof(buf) - 1)
+			rc = Respond(buf);
+	}
+
+	if (rc && strlen(Headers) > 0)
+		rc = Respond(headers);
+
+	tStrStrMap::iterator it = m_Params.begin();
+	while (rc && it != m_Params.end()) {
+		static const char DLNA_POSTFIX[] = ".dlna.org";
+		if (it->first.rfind(DLNA_POSTFIX) + sizeof(DLNA_POSTFIX) - 1 == it->first.length())
+			rc = Respond("%s: %s", true, it->first.c_str(), it->second.c_str());
+		++it;
+	}
+	return rc && Respond("");
+}
+
+bool cConnectionHTTP::ParseRange(int64_t &From, int64_t &To) const
+{
+	const static std::string RANGE("HTTP_RANGE");
+	From = To = 0L;
+	tStrStrMap::const_iterator it = Headers().find(RANGE);
+	if (it != Headers().end()) {
+		size_t b = it->second.find("bytes=");
+		if (b != std::string::npos) {
+			char* e = NULL;
+			const char* r = it->second.c_str() + b + sizeof("bytes=") - 1;
+			if (strchr(r, ',') != NULL)
+				esyslog("streamdev-server cConnectionHTTP::GetRange: Multi-ranges not supported");
+			From = strtol(r, &e, 10);
+			if (r != e) {
+				if (From < 0L) {
+					To = -1L;
+					return *e == 0 || *e == ',';
+				}
+				else if (*e == '-') {
+					r = e + 1;
+					if (*r == 0 || *e == ',') {
+						To = -1L;
+						return true;
+					}
+					To = strtol(r, &e, 10);
+					return r != e && To >= From &&
+							(*e == 0 || *e == ',');
+				}
+			}
+		}
+	}
+	return false;
 }
 
 void cConnectionHTTP::Flushed(void) 
@@ -231,21 +363,21 @@ void cConnectionHTTP::Flushed(void)
 	if (m_Status != hsBody)
 		return;
 
-	if (m_ChannelList) {
-		if (m_ChannelList->HasNext()) {
-			if (!Respond("%s", true, m_ChannelList->Next().c_str()))
+	if (m_MenuList) {
+		if (m_MenuList->HasNext()) {
+			if (!Respond("%s", true, m_MenuList->Next().c_str()))
 				DeferClose();
 		}
 		else {
-			DELETENULL(m_ChannelList);
+			DELETENULL(m_MenuList);
 			m_Status = hsFinished;
 			DeferClose();
 		}
 		return;
 	}
-	else if (m_Channel != NULL) {
+	else if (m_Streamer != NULL) {
 		Dprintf("streamer start\n");
-		m_LiveStreamer->Start(this);
+		m_Streamer->Start(this);
 		m_Status = hsFinished;
 	}
 	else {
@@ -255,57 +387,61 @@ void cConnectionHTTP::Flushed(void)
 	}
 }
 
-cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
+cMenuList* cConnectionHTTP::MenuListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
 {
-	// keys for Headers() hash
-	const static std::string QUERY_STRING("QUERY_STRING");
-	const static std::string HOST("HTTP_HOST");
-
-	tStrStrMap::const_iterator it_query = Headers().find(QUERY_STRING);
-	const std::string& query = it_query == Headers().end() ? "" : it_query->second;
-
 	std::string groupTarget;
-	cChannelIterator *iterator = NULL;
+	cItemIterator *iterator = NULL;
 
+	const static std::string GROUP("group");
 	if (Filebase.compare("tree") == 0) {
-		const cChannel* c = NULL;
-		size_t groupIndex = query.find("group=");
-		if (groupIndex != std::string::npos)
-			c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6));
-		iterator = new cListTree(c);
+		tStrStrMap::const_iterator it = m_Params.find(GROUP);
+		iterator = new cListTree(it == m_Params.end() ? NULL : it->second.c_str());
 		groupTarget = Filebase + Fileext;
 	} else if (Filebase.compare("groups") == 0) {
 		iterator = new cListGroups();
 		groupTarget = (std::string) "group" + Fileext;
 	} else if (Filebase.compare("group") == 0) {
-		const cChannel* c = NULL;
-		size_t groupIndex = query.find("group=");
-		if (groupIndex != std::string::npos)
-			c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6));
-		iterator = new cListGroup(c);
+		tStrStrMap::const_iterator it = m_Params.find(GROUP);
+		iterator = new cListGroup(it == m_Params.end() ? NULL : it->second.c_str());
 	} else if (Filebase.compare("channels") == 0) {
 		iterator = new cListChannels();
 	} else if (Filebase.compare("all") == 0 ||
 			(Filebase.empty() && Fileext.empty())) {
 		iterator = new cListAll();
+	} else if (Filebase.compare("recordings") == 0) {
+		iterator = new cRecordingsIterator();
 	}
 
 	if (iterator) {
+		// assemble base url: http://host/path/
+		std::string base;
+		const static std::string HOST("HTTP_HOST");
+		tStrStrMap::const_iterator it = Headers().find(HOST);
+		if (it != Headers().end())
+			base = "http://" + it->second + "/";
+		else
+			base = (std::string) "http://" + LocalIp() + ":" +
+				(const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/";
+		base += Path;
+
 		if (Filebase.empty() || Fileext.compare(".htm") == 0 || Fileext.compare(".html") == 0) {
 			std::string self = Filebase + Fileext;
-			if (!query.empty())
-				self += '?' + query;
-			return new cHtmlChannelList(iterator, m_StreamType, self.c_str(), groupTarget.c_str());
+			std::string rss = Filebase + ".rss";
+			tStrStrMap::const_iterator it = Headers().find("QUERY_STRING");
+			if (it != Headers().end() && !it->second.empty()) {
+				self += '?' + it->second;
+				rss += '?' + it->second;
+			}
+			return new cHtmlMenuList(iterator, m_StreamType, self.c_str(), rss.c_str(), groupTarget.c_str());
 		} else if (Fileext.compare(".m3u") == 0) {
-			std::string base;
-			tStrStrMap::const_iterator it = Headers().find(HOST);
-			if (it != Headers().end())
-				base = "http://" + it->second + "/";
-			else
-				base = (std::string) "http://" + LocalIp() + ":" +
-					(const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/";
-			base += Path;
-			return new cM3uChannelList(iterator, base.c_str());
+			return new cM3uMenuList(iterator, base.c_str());
+		} else if (Fileext.compare(".rss") == 0) {
+			std::string html = Filebase + ".html";
+			tStrStrMap::const_iterator it = Headers().find("QUERY_STRING");
+			if (it != Headers().end() && !it->second.empty()) {
+				html += '?' + it->second;
+			}
+			return new cRssMenuList(iterator, base.c_str(), html.c_str());
 		} else {
 			delete iterator;
 		}
@@ -313,6 +449,37 @@ cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, co
 	return NULL;
 }
 
+cRecording* cConnectionHTTP::RecordingFromString(const char *FileBase, const char *FileExt) const
+{
+	if (strcasecmp(FileExt, ".rec") != 0)
+		return NULL;
+
+	char *p = NULL;
+	unsigned long l = strtoul(FileBase, &p, 0);
+	if (p != FileBase && l > 0L) {
+		if (*p == ':') {
+			// get recording by dev:inode
+			unsigned long inode = strtoul(p + 1, &p, 0);
+			if (*p == 0 && inode > 0) {
+				struct stat st;
+				cThreadLock RecordingsLock(&Recordings);
+				for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) {
+					if (stat(rec->FileName(), &st) == 0 && st.st_dev == (dev_t) l && st.st_ino == (ino_t) inode)
+						return new cRecording(rec->FileName());
+				}
+			}
+		}
+		else if (*p == 0) {
+			// get recording by index
+			cThreadLock RecordingsLock(&Recordings);
+			cRecording* rec = Recordings.Get((int) l - 1);
+			if (rec)
+				return new cRecording(rec->FileName());
+		}
+	}
+	return NULL;
+}
+
 bool cConnectionHTTP::ProcessURI(const std::string& PathInfo) 
 {
 	std::string filespec, fileext;
@@ -349,9 +516,12 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
 
 	Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str());
 
-	if ((m_ChannelList = ChannelListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
+	if ((m_MenuList = MenuListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
 		Dprintf("Channel list requested\n");
 		return true;
+	} else if ((m_Recording = RecordingFromString(filespec.c_str(), fileext.c_str())) != NULL) {
+		Dprintf("Recording %s found\n", m_Recording->Name());
+		return true;
 	} else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) {
 		Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]);
 		return true;
@@ -362,5 +532,5 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
 cString cConnectionHTTP::ToText() const
 {
 	cString str = cServerConnection::ToText();
-	return m_LiveStreamer ? cString::sprintf("%s\t%s", *str, *m_LiveStreamer->ToText()) : str;
+	return m_Streamer ? cString::sprintf("%s\t%s", *str, *m_Streamer->ToText()) : str;
 }
diff --git a/server/connectionHTTP.h b/server/connectionHTTP.h
index 8f071ce..e946242 100644
--- a/server/connectionHTTP.h
+++ b/server/connectionHTTP.h
@@ -7,13 +7,13 @@
 
 #include "connection.h"
 #include "server/livestreamer.h"
+#include "server/recstreamer.h"
 
 #include <map>
 #include <tools/select.h>
 
 class cChannel;
-class cStreamdevLiveStreamer;
-class cChannelList;
+class cMenuList;
 
 class cConnectionHTTP: public cServerConnection {
 private:
@@ -26,17 +26,31 @@ private:
 
 	std::string                       m_Authorization;
 	eHTTPStatus                       m_Status;
+	tStrStrMap                        m_Params;
+	cStreamdevStreamer               *m_Streamer;
+	eStreamType                       m_StreamType;
 	// job: transfer
-	cStreamdevLiveStreamer           *m_LiveStreamer;
 	const cChannel                   *m_Channel;
 	int                               m_Apid[2];
 	int                               m_Dpid[2];
-	eStreamType                       m_StreamType;
+	// job: replay
+	cRecording                       *m_Recording;
 	// job: listing
-	cChannelList                     *m_ChannelList;
+	cMenuList                        *m_MenuList;
+
+	cMenuList* MenuListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const;
+	cRecording* RecordingFromString(const char* FileBase, const char* FileExt) const;
 
-	cChannelList* ChannelListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const;
 	bool ProcessURI(const std::string &PathInfo);
+	bool HttpResponse(int Code, bool Last, const char* ContentType = NULL, const char* Headers = "", ...);
+			//__attribute__ ((format (printf, 5, 6)));
+	/**
+	 * Extract byte range from HTTP Range header. Returns false if no valid
+	 * range is found. The contents of From and To are undefined in this
+	 * case. From may be negative in which case To is undefined.
+	 * TODO: support for multiple ranges.
+	 */
+	bool ParseRange(int64_t &From, int64_t &To) const;
 protected:
 	bool ProcessRequest(void);
 
@@ -44,8 +58,8 @@ public:
 	cConnectionHTTP(void);
 	virtual ~cConnectionHTTP();
 
-	virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
-	virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
+	virtual void Attach(void) { if (m_Streamer != NULL) m_Streamer->Attach(); }
+	virtual void Detach(void) { if (m_Streamer != NULL) m_Streamer->Detach(); }
 
 	virtual cString ToText() const;
 
@@ -59,7 +73,7 @@ public:
 
 inline bool cConnectionHTTP::Abort(void) const
 {
-	return !IsOpen() || (m_LiveStreamer && m_LiveStreamer->Abort());
+	return !IsOpen() || (m_Streamer && m_Streamer->Abort());
 }
 
 #endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
diff --git a/server/connectionVTP.c b/server/connectionVTP.c
index 39bf8de..57afafa 100644
--- a/server/connectionVTP.c
+++ b/server/connectionVTP.c
@@ -4,6 +4,7 @@
  
 #include "server/connectionVTP.h"
 #include "server/livestreamer.h"
+#include "server/livefilter.h"
 #include "server/suspend.h"
 #include "setup.h"
 
@@ -769,7 +770,7 @@ cConnectionVTP::~cConnectionVTP()
 bool cConnectionVTP::Abort(void) const
 {
 	return !IsOpen() || (m_LiveStreamer && m_LiveStreamer->Abort()) ||
-		(m_FilterStreamer && m_FilterStreamer->Abort());
+		(!m_LiveStreamer && m_FilterStreamer && m_FilterStreamer->Abort());
 }
 
 void cConnectionVTP::Welcome(void) 
@@ -1011,7 +1012,6 @@ bool cConnectionVTP::CmdPORT(char *Opts)
 		if(!m_FilterStreamer)
 			m_FilterStreamer = new cStreamdevFilterStreamer;
 		m_FilterStreamer->Start(m_FilterSocket);
-		m_FilterStreamer->Activate(true);
 
 		return Respond(220, "Port command ok, data connection opened");
 		break;
@@ -1662,7 +1662,7 @@ bool cConnectionVTP::CmdMOVC(const char *Option)
 								Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
 							}
 							else {
-								Reply(501, "Can't move channel to same postion");
+								Reply(501, "Can't move channel to same position");
 							}
 						}
 						else {
@@ -1815,7 +1815,11 @@ bool cConnectionVTP::Respond(int Code, const char *Message, ...)
 {
 	va_list ap;
 	va_start(ap, Message);
+#if APIVERSNUM >= 10728
+	cString str = cString::vsprintf(Message, ap);
+#else
 	cString str = cString::sprintf(Message, ap);
+#endif
 	va_end(ap);
 
 	if (Code >= 0 && m_LastCommand != NULL) {
diff --git a/server/livefilter.c b/server/livefilter.c
index 67b0e37..c159602 100644
--- a/server/livefilter.c
+++ b/server/livefilter.c
@@ -2,18 +2,52 @@
  *  $Id: livefilter.c,v 1.7 2009/02/13 13:02:40 schmirl Exp $
  */
 
+#include <vdr/filter.h>
+
 #include "server/livefilter.h"
-#include "server/streamer.h"
 #include "common.h"
 
 #ifndef TS_SYNC_BYTE
 #    define TS_SYNC_BYTE     0x47
 #endif
 
-cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevStreamer *Streamer) {
+#define FILTERBUFSIZE (1000 * TS_SIZE)
+
+// --- cStreamdevLiveFilter -------------------------------------------------
+
+class cStreamdevLiveFilter: public cFilter {
+private:
+	cStreamdevFilterStreamer *m_Streamer;
+	bool m_On;
+
+protected:
+	virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
+	virtual void SetStatus(bool On);
+
+public:
+	cStreamdevLiveFilter(cStreamdevFilterStreamer *Streamer);
+
+	virtual bool IsAttached(void) const { return m_On; };
+
+	void Set(u_short Pid, u_char Tid, u_char Mask) {
+		cFilter::Set(Pid, Tid, Mask);
+	}
+	void Del(u_short Pid, u_char Tid, u_char Mask) {
+		cFilter::Del(Pid, Tid, Mask);
+	}
+};
+
+cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevFilterStreamer *Streamer) {
+	m_On = false;
 	m_Streamer = Streamer;
 }
 
+void cStreamdevLiveFilter::SetStatus(bool On)
+{
+	m_On = On;
+	cFilter::SetStatus(On);
+}
+
 void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) 
 {
 	uchar buffer[TS_SIZE];
@@ -32,8 +66,86 @@ void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data,
 		length -= chunk;
 		pos += chunk;
 
-		int p = m_Streamer->Put(buffer, TS_SIZE);
-		if (p != TS_SIZE)
-			m_Streamer->ReportOverflow(TS_SIZE - p);
+		m_Streamer->Receive(buffer);
 	}
 }
+
+// --- cStreamdevFilterStreamer -------------------------------------------------
+
+cStreamdevFilterStreamer::cStreamdevFilterStreamer():
+		cStreamdevStreamer("streamdev-filterstreaming"),
+		m_Device(NULL),
+		m_Filter(NULL)/*,
+		m_Channel(NULL)*/
+{
+	m_ReceiveBuffer = new cStreamdevBuffer(FILTERBUFSIZE, TS_SIZE);
+	m_ReceiveBuffer->SetTimeouts(0, 500);
+}
+
+cStreamdevFilterStreamer::~cStreamdevFilterStreamer() 
+{
+	Dprintf("Desctructing Filter streamer\n");
+	Detach();
+	m_Device = NULL;
+	DELETENULL(m_Filter);
+	Stop();
+	delete m_ReceiveBuffer;
+}
+
+void cStreamdevFilterStreamer::Receive(uchar *Data)
+{
+	int p = m_ReceiveBuffer->PutTS(Data, TS_SIZE);
+	if (p != TS_SIZE)
+		m_ReceiveBuffer->ReportOverflow(TS_SIZE - p);
+}
+
+void cStreamdevFilterStreamer::Attach(void) 
+{ 
+	Dprintf("cStreamdevFilterStreamer::Attach()\n");
+	LOCK_THREAD;
+	if(m_Device && m_Filter)
+		m_Device->AttachFilter(m_Filter);
+}
+
+void cStreamdevFilterStreamer::Detach(void) 
+{ 
+	Dprintf("cStreamdevFilterStreamer::Detach()\n");
+	LOCK_THREAD;
+	if(m_Device && m_Filter)
+		m_Device->Detach(m_Filter); 
+}
+
+void cStreamdevFilterStreamer::SetDevice(cDevice *Device)
+{
+	Dprintf("cStreamdevFilterStreamer::SetDevice()\n");
+	LOCK_THREAD;
+	Detach();
+	m_Device = Device;
+	Attach();
+}
+
+bool cStreamdevFilterStreamer::IsReceiving(void) const
+{
+	return m_Filter && m_Filter->IsAttached();
+}
+
+bool cStreamdevFilterStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On) 
+{	
+	Dprintf("cStreamdevFilterStreamer::SetFilter(%u,0x%x,0x%x,%s)\n", Pid, Tid, Mask, On?"On":"Off");
+
+	if(!m_Device)
+		return false;
+
+	if (On) {
+		if (m_Filter == NULL) {
+			m_Filter = new cStreamdevLiveFilter(this);
+			Dprintf("attaching filter to device\n");
+			Attach();
+		}
+		m_Filter->Set(Pid, Tid, Mask);
+	} else if (m_Filter != NULL) 
+		m_Filter->Del(Pid, Tid, Mask);
+
+	return true;
+}
+
diff --git a/server/livefilter.h b/server/livefilter.h
index 13e8956..f3e2d6d 100644
--- a/server/livefilter.h
+++ b/server/livefilter.h
@@ -5,28 +5,33 @@
 #ifndef VDR_STREAMEV_LIVEFILTER_H
 #define VDR_STREAMEV_LIVEFILTER_H
 
-#include <vdr/config.h>
+#include "server/streamer.h"
 
-#include <vdr/filter.h>
+class cDevice;
+class cStreamdevLiveFilter;
 
-class cStreamdevStreamer;
-
-class cStreamdevLiveFilter: public cFilter {
+class cStreamdevFilterStreamer: public cStreamdevStreamer {
 private:
-	cStreamdevStreamer *m_Streamer;
+	cDevice                *m_Device;
+	cStreamdevLiveFilter   *m_Filter;
+	cStreamdevBuffer       *m_ReceiveBuffer;
 
 protected:
-	virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
+	virtual uchar* GetFromReceiver(int &Count) { return m_ReceiveBuffer->Get(Count); }
+	virtual void DelFromReceiver(int Count) { m_ReceiveBuffer->Del(Count); }
 
 public:
-	cStreamdevLiveFilter(cStreamdevStreamer *Streamer);
-
-	void Set(u_short Pid, u_char Tid, u_char Mask) {
-		cFilter::Set(Pid, Tid, Mask);
-	}
-	void Del(u_short Pid, u_char Tid, u_char Mask) {
-		cFilter::Del(Pid, Tid, Mask);
-	}
+	cStreamdevFilterStreamer();
+	virtual ~cStreamdevFilterStreamer();
+
+	void SetDevice(cDevice *Device);
+	bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On);
+
+	virtual bool IsReceiving(void) const;
+	void Receive(uchar *Data);
+	
+	virtual void Attach(void);
+	virtual void Detach(void);
 };
 
 #endif // VDR_STREAMEV_LIVEFILTER_H
diff --git a/server/livestreamer.c b/server/livestreamer.c
index 14d10c2..8ae9437 100644
--- a/server/livestreamer.c
+++ b/server/livestreamer.c
@@ -11,7 +11,6 @@
 #include <vdr/ringbuffer.h>
 
 #include "server/livestreamer.h"
-#include "server/livefilter.h"
 #include "common.h"
 
 using namespace Streamdev;
@@ -22,19 +21,17 @@ class cStreamdevLiveReceiver: public cReceiver {
 	friend class cStreamdevStreamer;
 
 private:
-	cStreamdevStreamer *m_Streamer;
+	cStreamdevLiveStreamer *m_Streamer;
 
 protected:
-	virtual void Activate(bool On);
 	virtual void Receive(uchar *Data, int Length);
 
 public:
-	cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids);
+	cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids);
 	virtual ~cStreamdevLiveReceiver();
 };
 
-cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, const cChannel *Channel, 
-                                               int Priority, const int *Pids):
+cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids):
 		cReceiver(Channel, Priority),
 		m_Streamer(Streamer)
 {
@@ -50,15 +47,7 @@ cStreamdevLiveReceiver::~cStreamdevLiveReceiver()
 }
 
 void cStreamdevLiveReceiver::Receive(uchar *Data, int Length) {
-	int p = m_Streamer->Receive(Data, Length);
-	if (p != Length)
-		m_Streamer->ReportOverflow(Length - p);
-}
-
-inline void cStreamdevLiveReceiver::Activate(bool On) 
-{ 
-	Dprintf("LiveReceiver->Activate(%d)\n", On);
-	m_Streamer->Activate(On); 
+	m_Streamer->Receive(Data, Length);
 }
 
 // --- cStreamdevPatFilter ----------------------------------------------------
@@ -349,18 +338,18 @@ cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, const cServerConnec
 		m_PatFilter(NULL),
 		m_Remux(NULL)
 {
+		m_ReceiveBuffer = new cStreamdevBuffer(LIVEBUFSIZE, TS_SIZE *2, true, "streamdev-livestreamer"),
+		m_ReceiveBuffer->SetTimeouts(0, 100);
 }
 
 cStreamdevLiveStreamer::~cStreamdevLiveStreamer() 
 {
 	Dprintf("Desctructing Live streamer\n");
 	Stop();
-	if(m_PatFilter) {
-		Detach();
-		DELETENULL(m_PatFilter);
-	}
+	DELETENULL(m_PatFilter);
 	DELETENULL(m_Receiver);
 	delete m_Remux;
+	delete m_ReceiveBuffer;
 }
 
 bool cStreamdevLiveStreamer::HasPid(int Pid) 
@@ -460,13 +449,19 @@ cString cStreamdevLiveStreamer::ToText() const
         return cString("");
 }
 
+bool cStreamdevLiveStreamer::IsReceiving(void) const
+{
+	cThreadLock ThreadLock(m_Device);
+	return m_Receiver && m_Receiver->IsAttached();
+}
+
 void cStreamdevLiveStreamer::StartReceiver(void)
 {
 	if (m_NumPids > 0) {
 		Dprintf("Creating Receiver to respect changed pids\n");
 		cReceiver *current = m_Receiver;
-		m_Receiver = new cStreamdevLiveReceiver(this, m_Channel, m_Priority, m_Pids);
 		cThreadLock ThreadLock(m_Device);
+		m_Receiver = new cStreamdevLiveReceiver(this, m_Channel, m_Priority, m_Pids);
 		if (IsRunning())
 			Attach();
 		delete current;
@@ -530,6 +525,13 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
 	}
 }
 
+void cStreamdevLiveStreamer::Receive(uchar *Data, int Length)
+{
+	int p = m_ReceiveBuffer->PutTS(Data, Length);
+	if (p != Length)
+		m_ReceiveBuffer->ReportOverflow(Length - p);
+}
+
 int cStreamdevLiveStreamer::Put(const uchar *Data, int Count) 
 {
 	// insert si data
@@ -609,105 +611,3 @@ std::string cStreamdevLiveStreamer::Report(void)
 	result += "\n";
 	return result;
 }
-
-// --- cStreamdevFilterStreamer -------------------------------------------------
-
-cStreamdevFilterStreamer::cStreamdevFilterStreamer():
-		cStreamdevStreamer("streamdev-filterstreaming"),
-		m_Device(NULL),
-		m_Filter(NULL)/*,
-		m_Channel(NULL)*/
-{
-}
-
-cStreamdevFilterStreamer::~cStreamdevFilterStreamer() 
-{
-	Dprintf("Desctructing Filter streamer\n");
-	Detach();
-	m_Device = NULL;
-	DELETENULL(m_Filter);
-	Stop();
-}
-
-void cStreamdevFilterStreamer::Attach(void) 
-{ 
-	Dprintf("cStreamdevFilterStreamer::Attach()\n");
-	LOCK_THREAD;
-	if(m_Device && m_Filter)
-		m_Device->AttachFilter(m_Filter);
-}
-
-void cStreamdevFilterStreamer::Detach(void) 
-{ 
-	Dprintf("cStreamdevFilterStreamer::Detach()\n");
-	LOCK_THREAD;
-	if(m_Device && m_Filter)
-		m_Device->Detach(m_Filter); 
-}
-
-#if 0
-void cStreamdevFilterStreamer::SetChannel(const cChannel *Channel)
-{
-	LOCK_THREAD;
-	Dprintf("cStreamdevFilterStreamer::SetChannel(%s : %s)", Channel?Channel->Name():"<null>",
-		Channel ? *Channel->GetChannelID().ToString() : "");
-	m_Channel = Channel;
-}
-#endif
-
-void cStreamdevFilterStreamer::SetDevice(cDevice *Device)
-{
-	Dprintf("cStreamdevFilterStreamer::SetDevice()\n");
-	LOCK_THREAD;
-	Detach();
-	m_Device = Device;
-	//m_Channel = NULL;
-	Attach();
-}
-
-bool cStreamdevFilterStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On) 
-{	
-	Dprintf("cStreamdevFilterStreamer::SetFilter(%u,0x%x,0x%x,%s)\n", Pid, Tid, Mask, On?"On":"Off");
-
-	if(!m_Device)
-		return false;
-
-	if (On) {
-		if (m_Filter == NULL) {
-			m_Filter = new cStreamdevLiveFilter(this);
-			Dprintf("attaching filter to device\n");
-			Attach();
-		}
-		m_Filter->Set(Pid, Tid, Mask);
-	} else if (m_Filter != NULL) 
-		m_Filter->Del(Pid, Tid, Mask);
-
-	return true;
-}
-
-#if 0
-void cStreamdevFilterStreamer::ChannelSwitch(const cDevice *Device, int ChannelNumber) {
-	LOCK_THREAD;
-	if(Device == m_Device) {
-		if(ChannelNumber > 0) {
-			cChannel *ch = Channels.GetByNumber(ChannelNumber);
-			if(ch != NULL) {
-				if(m_Filter != NULL &&
-						m_Channel != NULL &&
-						(! TRANSPONDER(ch, m_Channel))) {
-
-					isyslog("***** LiveFilterStreamer: transponder changed ! %s",
-						*ch->GetChannelID().ToString());
-
-					uchar buffer[TS_SIZE] = {TS_SYNC_BYTE, 0xff, 0xff, 0xff, 0x7f, 0};
-					strcpy((char*)(buffer + 5), ch->GetChannelID().ToString());
-					int p = Put(buffer, TS_SIZE);
-					if (p != TS_SIZE)
-						ReportOverflow(TS_SIZE - p);
-				}
-				m_Channel = ch;
-			}
-		}
-	}
-}
-#endif
diff --git a/server/livestreamer.h b/server/livestreamer.h
index 037515c..db546ec 100644
--- a/server/livestreamer.h
+++ b/server/livestreamer.h
@@ -7,6 +7,8 @@
 #include "server/streamer.h"
 #include "common.h"
 
+#define LIVEBUFSIZE (20000 * TS_SIZE)
+
 namespace Streamdev {
 	class cTSRemux;
 }
@@ -24,12 +26,19 @@ private:
 	const cChannel         *m_Channel;
 	cDevice                *m_Device;
 	cStreamdevLiveReceiver *m_Receiver;
+	cStreamdevBuffer       *m_ReceiveBuffer;
 	cStreamdevPatFilter    *m_PatFilter;
 	Streamdev::cTSRemux    *m_Remux;
 
 	void StartReceiver(void);
 	bool HasPid(int Pid);
 
+protected:
+	virtual uchar* GetFromReceiver(int &Count) { return m_ReceiveBuffer->Get(Count); }
+	virtual void DelFromReceiver(int Count) { m_ReceiveBuffer->Del(Count); }
+
+	virtual int Put(const uchar *Data, int Count);
+
 public:
 	cStreamdevLiveStreamer(int Priority, const cServerConnection *Connection);
 	virtual ~cStreamdevLiveStreamer();
@@ -40,9 +49,11 @@ public:
 	bool SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL);
 	void SetPriority(int Priority);
 	void GetSignal(int *DevNum, int *Strength, int *Quality) const;
-	cString ToText() const;
+	virtual cString ToText() const;
 	
-	virtual int Put(const uchar *Data, int Count);
+	void Receive(uchar *Data, int Length);
+	virtual bool IsReceiving(void) const;
+
 	virtual uchar *Get(int &Count);
 	virtual void Del(int Count);
 
@@ -53,32 +64,4 @@ public:
 	virtual std::string Report(void);
 };
 
-
-// --- cStreamdevFilterStreamer -------------------------------------------------
-
-//#include <vdr/status.h>
-
-class cStreamdevLiveFilter;
-
-class cStreamdevFilterStreamer: public cStreamdevStreamer /*, public cStatus*/ {
-private:
-	cDevice                *m_Device;
-	cStreamdevLiveFilter   *m_Filter;
-	//const cChannel         *m_Channel;
-
-public:
-	cStreamdevFilterStreamer();
-	virtual ~cStreamdevFilterStreamer();
-
-	void SetDevice(cDevice *Device);
-	//void SetChannel(const cChannel *Channel);
-	bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On);
-	
-	virtual void Attach(void);
-	virtual void Detach(void);
-
-	// cStatus message handlers
-	//virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
-};
-
 #endif // VDR_STREAMDEV_LIVESTREAMER_H
diff --git a/server/menuHTTP.c b/server/menuHTTP.c
index d7fb817..de46f00 100644
--- a/server/menuHTTP.c
+++ b/server/menuHTTP.c
@@ -1,17 +1,92 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
 #include <vdr/channels.h>
 #include "server/menuHTTP.h"
 
+//**************************** cRecordingIterator **************
+cRecordingsIterator::cRecordingsIterator(): RecordingsLock(&Recordings)
+{	
+	first = Recordings.First();
+	current = NULL;
+}
+
+bool cRecordingsIterator::Next()
+{
+	if (first)
+	{
+		current = first;
+		first = NULL;
+	}
+	else
+		current = Recordings.Next(current);
+	return current;
+}
+
+const cString cRecordingsIterator::ItemRessource() const
+{
+	struct stat st;
+	if (stat(current->FileName(), &st) == 0)
+		return cString::sprintf("%lu:%lu.rec", st.st_dev, st.st_ino);
+	return "";
+}
+
 //**************************** cChannelIterator **************
-cChannelIterator::cChannelIterator(const cChannel *First): channel(First)
-{}
+cChannelIterator::cChannelIterator(const cChannel *First)
+{	
+	first = First;
+	current = NULL;
+}
 
-const cChannel* cChannelIterator::Next()
+bool cChannelIterator::Next()
 {
-	const cChannel *current = channel;
-	channel = NextChannel(channel);
+	if (first)
+	{
+		current = first;
+		first = NULL;
+	}
+	else
+		current = NextChannel(current);
 	return current;
 }
 
+const cString cChannelIterator::ItemId() const
+{
+	if (current)
+	{
+		if (current->GroupSep())
+		{
+			int index = 0;
+			for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr))
+			{
+				if (Channels.Get(curr) == current)
+					return itoa(index);
+				index++;
+			}
+		}
+		else
+		{
+			return itoa(current->Number());
+		}
+	}
+	return cString("-1");
+}
+
+const cChannel* cChannelIterator::GetGroup(const char* GroupId)
+{
+	int group = -1;
+	if (GroupId)
+	{
+		int Index = atoi(GroupId);
+		group = Channels.GetNextGroup(-1);
+		while (Index-- && group >= 0)
+			group = Channels.GetNextGroup(group);
+	}
+	return group >= 0 ? Channels.Get(group) : NULL;
+}
+
+
 //**************************** cListAll **************
 cListAll::cListAll(): cChannelIterator(Channels.First())
 {}
@@ -46,7 +121,7 @@ const cChannel* cListGroups::NextChannel(const cChannel *Channel)
 }
 //
 // ********************* cListGroup ****************
-cListGroup::cListGroup(const cChannel *Group): cChannelIterator(GetNextChannelInGroup(Group))
+cListGroup::cListGroup(const char *GroupId): cChannelIterator(GetNextChannelInGroup(GetGroup(GroupId)))
 {}
 
 const cChannel* cListGroup::GetNextChannelInGroup(const cChannel *Channel)
@@ -62,9 +137,9 @@ const cChannel* cListGroup::NextChannel(const cChannel *Channel)
 }
 //
 // ********************* cListTree ****************
-cListTree::cListTree(const cChannel *SelectedGroup): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
+cListTree::cListTree(const char *SelectedGroupId): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
 {
-	selectedGroup = SelectedGroup;
+	selectedGroup = GetGroup(SelectedGroupId);
 	currentGroup = Channels.Get(Channels.GetNextGroup(-1));
 }
 
@@ -86,43 +161,24 @@ const cChannel* cListTree::NextChannel(const cChannel *Channel)
 	return Channel;
 }
 
-// ******************** cChannelList ******************
-cChannelList::cChannelList(cChannelIterator *Iterator) : iterator(Iterator)
+// ******************** cMenuList ******************
+cMenuList::cMenuList(cItemIterator *Iterator) : iterator(Iterator)
 {}
 
-cChannelList::~cChannelList()
+cMenuList::~cMenuList()
 {
 	delete iterator;
 }
 
-int cChannelList::GetGroupIndex(const cChannel *Group)
-{
-	int index = 0;
-	for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr))
-	{
-		if (Channels.Get(curr) == Group)
-			return index;
-		index++;
-	}
-	return -1;
-}
-
-const cChannel* cChannelList::GetGroup(int Index)
-{
-	int group = Channels.GetNextGroup(-1);
-	while (Index-- && group >= 0)
-		group = Channels.GetNextGroup(group);
-	return group >= 0 ? Channels.Get(group) : NULL;
-}
-
-// ******************** cHtmlChannelList ******************
-const char* cHtmlChannelList::menu =
+// ******************** cHtmlMenuList ******************
+const char* cHtmlMenuList::menu =
 	"[<a href=\"/\">Home</a> (<a href=\"all.html\" tvid=\"RED\">no script</a>)] "
 	"[<a href=\"tree.html\" tvid=\"GREEN\">Tree View</a>] "
-	"[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
-	"[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
+	"[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a> | <a href=\"groups.rss\">RSS</a>)] "
+	"[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a> | <a href=\"channels.rss\">RSS</a>)] "
+	"[<a href=\"recordings.html\">Recordings</a> (<a href=\"recordings.m3u\">Playlist</a> | <a href=\"recordings.rss\">RSS</a>)] ";
 
-const char* cHtmlChannelList::css =
+const char* cHtmlMenuList::css =
 	"<style type=\"text/css\">\n"
 	"<!--\n"
 	"a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n"
@@ -138,7 +194,7 @@ const char* cHtmlChannelList::css =
 	"-->\n"
 	"</style>";
 
-const char* cHtmlChannelList::js =
+const char* cHtmlMenuList::js =
 	"<script language=\"JavaScript\">\n"
 	"<!--\n"
 
@@ -199,7 +255,7 @@ const char* cHtmlChannelList::js =
 	"</script>";
 
 
-std::string cHtmlChannelList::StreamTypeMenu()
+std::string cHtmlMenuList::StreamTypeMenu()
 {
 	std::string typeMenu;
 	typeMenu += (streamType == stTS ? (std::string) "[TS] " :
@@ -215,27 +271,29 @@ std::string cHtmlChannelList::StreamTypeMenu()
 	return typeMenu;
 }
 
-cHtmlChannelList::cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget): cChannelList(Iterator)
+cHtmlMenuList::cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget): cMenuList(Iterator)
 {
 	streamType = StreamType;
 	self = strdup(Self);
+	rss = strdup(Rss);
 	groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL;
 	htmlState = hsRoot;
-	current = NULL;
+	onItem = true;
 }
 
-cHtmlChannelList::~cHtmlChannelList()
+cHtmlMenuList::~cHtmlMenuList()
 {
 	free((void *) self);
+	free((void *) rss);
 	free((void *) groupTarget);
 }
 
-bool cHtmlChannelList::HasNext()
+bool cHtmlMenuList::HasNext()
 {
 	return htmlState != hsPageBottom;
 }
 
-std::string cHtmlChannelList::Next()
+std::string cHtmlMenuList::Next()
 {
 	switch (htmlState)
 	{
@@ -252,39 +310,39 @@ std::string cHtmlChannelList::Next()
 			htmlState = hsPageTop;
 			break;
 		case hsPageTop:
-			current = NextChannel();
-			htmlState = current ? (current->GroupSep() ? hsGroupTop : hsPlainTop) : hsPageBottom;
+			onItem = NextItem();
+			htmlState = onItem ? (IsGroup() ? hsGroupTop : hsPlainTop) : hsPageBottom;
 			break;
 		case hsPlainTop:
 			htmlState = hsPlainItem;
 			break;
 		case hsPlainItem:
-			current = NextChannel();
-			htmlState = current && !current->GroupSep() ? hsPlainItem : hsPlainBottom;
+			onItem = NextItem();
+			htmlState = onItem && !IsGroup() ? hsPlainItem : hsPlainBottom;
 			break;
 		case hsPlainBottom:
-			htmlState = current ? hsGroupTop : hsPageBottom;
+			htmlState = onItem ? hsGroupTop : hsPageBottom;
 			break;
 		case hsGroupTop:
-			current = NextChannel();
-			htmlState = current && !current->GroupSep() ? hsItemsTop : hsGroupBottom;
+			onItem = NextItem();
+			htmlState = onItem && !IsGroup() ? hsItemsTop : hsGroupBottom;
 			break;
 		case hsItemsTop:
 			htmlState = hsItem;
 			break;
 		case hsItem:
-			current = NextChannel();
-			htmlState = current && !current->GroupSep() ? hsItem : hsItemsBottom;
+			onItem = NextItem();
+			htmlState = onItem && !IsGroup() ? hsItem : hsItemsBottom;
 			break;
 		case hsItemsBottom:
 			htmlState = hsGroupBottom;
 			break;
 		case hsGroupBottom:
-			htmlState = current ? hsGroupTop : hsPageBottom;
+			htmlState = onItem ? hsGroupTop : hsPageBottom;
 			break;
 		case hsPageBottom:
 		default:
-			esyslog("streamdev-server cHtmlChannelList: invalid call to Next()");
+			esyslog("streamdev-server cHtmlMenuList: invalid call to Next()");
 			break;
 	}
 	switch (htmlState)
@@ -309,36 +367,35 @@ std::string cHtmlChannelList::Next()
 	}
 }
 
-std::string cHtmlChannelList::HtmlHead()
+std::string cHtmlMenuList::HtmlHead()
 {
-	return (std::string) "";
+	return (std::string) "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"" + rss + "\"/>";
 }
 
-std::string cHtmlChannelList::PageTop()
+std::string cHtmlMenuList::PageTop()
 {
 	return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>";
 }
 
-std::string cHtmlChannelList::PageBottom()
+std::string cHtmlMenuList::PageBottom()
 {
 	return (std::string) "";
 }
 
-std::string cHtmlChannelList::GroupTitle()
+std::string cHtmlMenuList::GroupTitle()
 {
 	if (groupTarget)
 	{
-		return (std::string) "<a href=\"" + groupTarget + "?group=" +
-			(const char*) itoa(cChannelList::GetGroupIndex(current)) +
-			"\">" + current->Name() + "</a>";
+		return (std::string) "<a href=\"" + groupTarget + "?group=" + (const char*) ItemId() + "\">" +
+			ItemTitle() + "</a>";
 	}
 	else
 	{
-		return (std::string) current->Name();
+		return (std::string) ItemTitle();
 	}
 }
 
-std::string cHtmlChannelList::ItemText()
+std::string cHtmlMenuList::ItemText()
 {
 	std::string line;
 	std::string suffix;
@@ -350,58 +407,58 @@ std::string cHtmlChannelList::ItemText()
 		case stPES: suffix = (std::string) ".vdr"; break; 
 		default: suffix = "";
 	}
-	line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
-	line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + suffix + "\"";
+	line += (std::string) "<li value=\"" + (const char*) ItemId() + "\">";
+	line += (std::string) "<a href=\"" + (const char*) ItemRessource() + suffix + "\"";
 
 	// for Network Media Tank
 	line += (std::string) " vod ";
-	if (current->Number() < 1000)
-	    line += (std::string) " tvid=\"" + (const char*) itoa(current->Number()) + "\""; 
-
-	line += (std::string) ">" + current->Name() + "</a>";
+	if (strlen(ItemId()) < 4)
+	    line += (std::string) " tvid=\"" + (const char*) ItemId() + "\""; 
 
-	int count = 0;
-	for (int i = 0; current->Apid(i) != 0; ++i, ++count)
-		;
-	for (int i = 0; current->Dpid(i) != 0; ++i, ++count)
-		;
+	line += (std::string) ">" + ItemTitle() + "</a>";
 
-	if (count > 1)
+	// TS always streams all PIDs
+	if (streamType != stTS)
 	{
 		int index = 1;
-		for (int i = 0; current->Apid(i) != 0; ++i, ++index) {
-			line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
-					"+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + current->Alang(i) + "</a>";
-			}
-		for (int i = 0; current->Dpid(i) != 0; ++i, ++index) {
-			line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
-					"+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + current->Dlang(i) + "</a>";
-			}
+		const char* lang;
+		std::string pids;
+		for (int i = 0; (lang = Alang(i)) != NULL; ++i, ++index) {
+			pids += (std::string) " <a href=\"" + (const char*) ItemRessource() +
+					"+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + (const char*) lang + "</a>";
+		}
+		for (int i = 0; (lang = Dlang(i)) != NULL; ++i, ++index) {
+			pids += (std::string) " <a href=\"" + (const char*) ItemRessource() +
+					"+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + (const char*) lang + "</a>";
+		}
+		// always show audio PIDs for stES to select audio only
+		if (index > 2 || streamType == stES)
+			line += pids;
 	}
 	line += "</li>";
 	return line;
 }
 
-// ******************** cM3uChannelList ******************
-cM3uChannelList::cM3uChannelList(cChannelIterator *Iterator, const char* Base)
-: cChannelList(Iterator),
+// ******************** cM3uMenuList ******************
+cM3uMenuList::cM3uMenuList(cItemIterator *Iterator, const char* Base)
+: cMenuList(Iterator),
   m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
 {
 	base = strdup(Base);
 	m3uState = msFirst;
 }
 
-cM3uChannelList::~cM3uChannelList()
+cM3uMenuList::~cM3uMenuList()
 {
 	free(base);
 }
 
-bool cM3uChannelList::HasNext()
+bool cM3uMenuList::HasNext()
 {
 	return m3uState != msLast;
 }
 
-std::string cM3uChannelList::Next()
+std::string cM3uMenuList::Next()
 {
 	if (m3uState == msFirst)
 	{
@@ -409,26 +466,83 @@ std::string cM3uChannelList::Next()
 		return "#EXTM3U";
 	}
 
-	const cChannel *channel = NextChannel();
-	if (!channel)
+	if (!NextItem())
 	{
 		m3uState = msLast;
 		return "";
 	}
 
-	std::string name = (std::string) m_IConv.Convert(channel->Name());
+	std::string name = (std::string) m_IConv.Convert(ItemTitle());
 
-	if (channel->GroupSep())
+	if (IsGroup())
 	{
 		return (std::string) "#EXTINF:-1," + name + "\r\n" +
-			base + "group.m3u?group=" +
-			(const char*) itoa(cChannelList::GetGroupIndex(channel));
+			base + "group.m3u?group=" + (const char*) ItemId();
 	}
 	else
 	{
 		return (std::string) "#EXTINF:-1," +
-			(const char*) itoa(channel->Number()) + " " + name + "\r\n" +
-			base + (std::string) channel->GetChannelID().ToString();
+			(const char*) ItemId() + " " + name + "\r\n" +
+			base + (const char*) ItemRessource();
+	}
+}
+
+// ******************** cRssMenuList ******************
+cRssMenuList::cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html)
+: cMenuList(Iterator),
+  m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
+{
+	base = strdup(Base);
+	html = strdup(Html);
+	rssState = msFirst;
+}
+
+cRssMenuList::~cRssMenuList()
+{
+	free(base);
+	free(html);
+}
+
+bool cRssMenuList::HasNext()
+{
+	return rssState != msLast;
+}
+
+std::string cRssMenuList::Next()
+{
+	std::string type_ext;
+
+	if (rssState == msFirst)
+	{
+		rssState = msContinue;
+		return (std::string) "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\">\n\t<channel>\n"
+				"\t\t<title>VDR</title>\n"
+				"\t\t<link>" + base + html + "</link>\n"
+				"\t\t<description>VDR channel list</description>\n"
+				;
+	}
+
+	if (!NextItem())
+	{
+		rssState = msLast;
+		return "\t</channel>\n</rss>\n";
+	}
+
+	std::string name = (std::string) m_IConv.Convert(ItemTitle());
+
+	if (IsGroup())
+	{
+		return (std::string) "\t\t<item>\n\t\t\t<title>" +
+			name + "</title>\n\t\t\t<link>" +
+			base + "group.rss?group=" + (const char*) ItemId() + "</link>\n\t\t</item>\n";
+	}
+	else
+	{
+		return (std::string) "\t\t<item>\n\t\t\t<title>" +
+			(const char*) ItemId() + " " + name + "</title>\n\t\t\t<link>" +
+			base + (const char*) ItemRessource() + "</link>\n\t\t\t<enclosure url=\"" +
+			base + (const char*) ItemRessource() + "\" type=\"video/mpeg\" />\n\t\t</item>\n";
 	}
 }
 
+
diff --git a/server/menuHTTP.h b/server/menuHTTP.h
index 4ef6363..e6141e0 100644
--- a/server/menuHTTP.h
+++ b/server/menuHTTP.h
@@ -3,19 +3,59 @@
 
 #include <string>
 #include "../common.h"
+#include <vdr/recording.h>
 
 class cChannel;
 
-// ******************** cChannelIterator ******************
-class cChannelIterator
+// ******************** cItemIterator ******************
+class cItemIterator
+{
+	public:
+		virtual bool Next() = 0;
+		virtual bool IsGroup() const = 0;
+		virtual const cString ItemId() const = 0;
+		virtual const char* ItemTitle() const = 0;
+		virtual const cString ItemRessource() const = 0;
+		virtual const char* Alang(int i) const = 0;
+		virtual const char* Dlang(int i) const = 0;
+};
+
+class cRecordingsIterator: public cItemIterator
+{
+	private:
+		const cRecording *first;
+		const cRecording *current;
+		cThreadLock RecordingsLock;
+	public:
+		virtual bool Next();
+		virtual bool IsGroup() const { return false; }
+		virtual const cString ItemId() const { return current ? itoa(current->Index() + 1) : "0"; }
+		virtual const char* ItemTitle() const { return current ? current->Title() : ""; }
+		virtual const cString ItemRessource() const;
+		virtual const char* Alang(int i) const { return NULL; }
+		virtual const char* Dlang(int i) const { return NULL; }
+		cRecordingsIterator();
+		virtual ~cRecordingsIterator() {};
+};
+
+class cChannelIterator: public cItemIterator
 {
 	private:
-		const cChannel *channel;
+		const cChannel *first;
+		const cChannel *current;
 	protected:
 		virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
 		static inline const cChannel* SkipFakeGroups(const cChannel *Channel);
+		// Helper which returns the group by its index
+		static const cChannel* GetGroup(const char* GroupId);
 	public:
-		const cChannel* Next();
+		virtual bool Next();
+		virtual bool IsGroup() const { return current && current->GroupSep(); }
+		virtual const cString ItemId() const;
+		virtual const char* ItemTitle() const { return current ? current->Name() : ""; }
+		virtual const cString ItemRessource() const { return (current ? current->GetChannelID() : tChannelID::InvalidID).ToString(); }
+		virtual const char* Alang(int i) const { return current && current->Apid(i) ? current->Alang(i) : NULL; }
+		virtual const char* Dlang(int i) const { return current && current->Dpid(i) ? current->Dlang(i) : NULL; }
 		cChannelIterator(const cChannel *First);
 		virtual ~cChannelIterator() {};
 };
@@ -54,7 +94,7 @@ class cListGroup: public cChannelIterator
 	protected:
 		virtual const cChannel* NextChannel(const cChannel *Channel);
 	public:
-		cListGroup(const cChannel *Group);
+		cListGroup(const char *GroupId);
 		virtual ~cListGroup() {};
 };
 
@@ -66,31 +106,32 @@ class cListTree: public cChannelIterator
 	protected:
 		virtual const cChannel* NextChannel(const cChannel *Channel);
 	public:
-		cListTree(const cChannel *SelectedGroup);
+		cListTree(const char *SelectedGroupId);
 		virtual ~cListTree() {};
 };
 
-// ******************** cChannelList ******************
-class cChannelList
+// ******************** cMenuList ******************
+class cMenuList
 {
 	private:
-		cChannelIterator *iterator;
+		cItemIterator *iterator;
 	protected:
-		const cChannel* NextChannel() { return iterator->Next(); }
+		bool NextItem() { return iterator->Next(); }
+		bool IsGroup() { return iterator->IsGroup(); }
+		const cString ItemId() { return iterator->ItemId(); }
+		const char* ItemTitle() { return iterator->ItemTitle(); }
+		const cString ItemRessource() { return iterator->ItemRessource(); }
+		const char* Alang(int i) { return iterator->Alang(i); }
+		const char* Dlang(int i) { return iterator->Dlang(i); }
 	public:
-		// Helper which returns the group index
-		static int GetGroupIndex(const cChannel* Group);
-		// Helper which returns the group by its index
-		static const cChannel* GetGroup(int Index);
-
 		virtual std::string HttpHeader() { return "HTTP/1.0 200 OK\r\n"; };
 		virtual bool HasNext() = 0;
 		virtual std::string Next() = 0;
-		cChannelList(cChannelIterator *Iterator);
-		virtual ~cChannelList();
+		cMenuList(cItemIterator *Iterator);
+		virtual ~cMenuList();
 };
 
-class cHtmlChannelList: public cChannelList
+class cHtmlMenuList: public cMenuList
 {
 	private:
 		static const char* menu;
@@ -104,9 +145,10 @@ class cHtmlChannelList: public cChannelList
 			hsItemsTop, hsItem, hsItemsBottom 
 		};
 		eHtmlState htmlState;
-		const cChannel *current;
+		bool onItem;
 		eStreamType streamType;
 		const char* self;
+		const char* rss;
 		const char* groupTarget;
 
 		std::string StreamTypeMenu();
@@ -117,18 +159,18 @@ class cHtmlChannelList: public cChannelList
 		std::string PageBottom();
 	public:
 		virtual std::string HttpHeader() {
-			return cChannelList::HttpHeader()
+			return cMenuList::HttpHeader()
 				+ "Content-type: text/html; charset="
 				+ (cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8")
 				+ "\r\n";
 		}
 		virtual bool HasNext();
 		virtual std::string Next();
-		cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget);
-		virtual ~cHtmlChannelList();
+		cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget);
+		virtual ~cHtmlMenuList();
 };
 
-class cM3uChannelList: public cChannelList
+class cM3uMenuList: public cMenuList
 {
 	private:
 		char *base;
@@ -136,11 +178,28 @@ class cM3uChannelList: public cChannelList
 		eM3uState m3uState;
 		cCharSetConv m_IConv;
 	public:
-		virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\r\n"; };
+		virtual std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\r\n"; };
+		virtual bool HasNext();
+		virtual std::string Next();
+		cM3uMenuList(cItemIterator *Iterator, const char* Base);
+		virtual ~cM3uMenuList();
+};
+
+class cRssMenuList: public cMenuList
+{
+	private:
+		char *base;
+		char *html;
+		enum eRssState { msFirst, msContinue, msLast };
+		eRssState rssState;
+		cCharSetConv m_IConv;
+	public:
+		virtual std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: application/rss+xml\r\n"; };
 		virtual bool HasNext();
 		virtual std::string Next();
-		cM3uChannelList(cChannelIterator *Iterator, const char* Base);
-		virtual ~cM3uChannelList();
+		
+		cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html);
+		virtual ~cRssMenuList();
 };
 
 inline const cChannel* cChannelIterator::SkipFakeGroups(const cChannel* Group)
diff --git a/server/po/it_IT.po b/server/po/it_IT.po
index cb00f29..dc68082 100644
--- a/server/po/it_IT.po
+++ b/server/po/it_IT.po
@@ -9,14 +9,14 @@ msgid ""
 msgstr ""
 "Project-Id-Version: streamdev 0.5.0\n"
 "Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
-"POT-Creation-Date: 2012-03-31 15:06+0200\n"
-"PO-Revision-Date: 2010-06-19 03:58+0100\n"
+"POT-Creation-Date: 2012-06-13 08:51+0200\n"
+"PO-Revision-Date: 2012-06-12 19:57+0100\n"
 "Last-Translator: Diego Pierotto <vdr-italian at tiscali.it>\n"
 "Language-Team: Italian <vdr at linuxtv.org>\n"
-"Language: it\n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
+"Language: it\n"
 
 msgid "VDR Streaming Server"
 msgstr "Server trasmissione VDR"
@@ -25,10 +25,10 @@ msgid "Streaming active"
 msgstr "Trasmissione attiva"
 
 msgid "Streamdev Connections"
-msgstr ""
+msgstr "Connessioni Streamdev"
 
 msgid "Disconnect"
-msgstr ""
+msgstr "Disconnetti"
 
 msgid "Suspend"
 msgstr "Sospendi"
@@ -55,13 +55,13 @@ msgid "Bind to IP"
 msgstr "IP associati"
 
 msgid "Legacy Client Priority"
-msgstr ""
+msgstr "Priorità nativa client"
 
 msgid "Client may suspend"
 msgstr "Permetti sospensione al Client"
 
 msgid "Loop Prevention"
-msgstr ""
+msgstr "Evita ciclo"
 
 msgid "HTTP Server"
 msgstr "Server HTTP"
@@ -73,7 +73,7 @@ msgid "HTTP Server Port"
 msgstr "Porta Server HTTP"
 
 msgid "Priority"
-msgstr ""
+msgstr "Priorità"
 
 msgid "HTTP Streamtype"
 msgstr "Tipo flusso HTTP"
diff --git a/server/recstreamer.c b/server/recstreamer.c
new file mode 100644
index 0000000..73af53b
--- /dev/null
+++ b/server/recstreamer.c
@@ -0,0 +1,67 @@
+#include "remux/ts2ps.h"
+#include "remux/ts2pes.h"
+#include "remux/ts2es.h"
+#include "remux/extern.h"
+
+#include <vdr/ringbuffer.h>
+#include "server/recstreamer.h"
+#include "server/connection.h"
+#include "common.h"
+
+using namespace Streamdev;
+
+// --- cStreamdevRecStreamer -------------------------------------------------
+
+cStreamdevRecStreamer::cStreamdevRecStreamer(cRecording *Rec, const cServerConnection *Connection):
+		cStreamdevStreamer("streamdev-recstreaming", Connection),
+		m_RecPlayer(Rec),
+		m_From(0L)
+{
+	Dprintf("New rec streamer\n");
+	m_To = (int64_t) m_RecPlayer.getLengthBytes() - 1;
+}
+
+cStreamdevRecStreamer::~cStreamdevRecStreamer() 
+{
+	Dprintf("Desctructing rec streamer\n");
+	Stop();
+}
+
+int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To)
+{
+	int64_t l = (int64_t) m_RecPlayer.getLengthBytes();
+	if (From < 0L) {
+		From += l;
+		if (From < 0L)
+			From = 0L;
+		To = l - 1;
+	}
+	else {
+		if (To < 0L)
+			To += l;
+		else if (To >= l)
+			To = l - 1;
+		if (From > To) {
+			// invalid range - return whole content
+			From = 0L;
+			To = l - 1;
+		}
+	}
+	m_From = From;
+	m_To = To;
+	return m_To - m_From + 1;
+}
+
+uchar* cStreamdevRecStreamer::GetFromReceiver(int &Count)
+{
+	if (m_From <= m_To) {
+		Count = (int) m_RecPlayer.getBlock(m_Buffer, m_From, sizeof(m_Buffer));
+		return m_Buffer;
+	}
+	return NULL;
+}
+
+cString cStreamdevRecStreamer::ToText() const
+{
+	return "REPLAY";
+}
diff --git a/server/recstreamer.h b/server/recstreamer.h
new file mode 100644
index 0000000..83df8c1
--- /dev/null
+++ b/server/recstreamer.h
@@ -0,0 +1,32 @@
+#ifndef VDR_STREAMDEV_RECSTREAMER_H
+#define VDR_STREAMDEV_RECSTREAMER_H
+
+#include "server/streamer.h"
+#include "server/recplayer.h"
+
+#define RECBUFSIZE (174 * TS_SIZE)
+
+// --- cStreamdevRecStreamer -------------------------------------------------
+
+class cStreamdevRecStreamer: public cStreamdevStreamer {
+private:
+	//Streamdev::cTSRemux    *m_Remux;
+	RecPlayer               m_RecPlayer;
+	int64_t                 m_From;
+	int64_t                 m_To;
+	uchar                   m_Buffer[RECBUFSIZE];
+
+protected:
+	virtual uchar* GetFromReceiver(int &Count);
+	virtual void DelFromReceiver(int Count) { m_From += Count; };
+
+public:
+	virtual bool IsReceiving(void) const { return m_From <= m_To; };
+	inline uint64_t GetLength() { return m_RecPlayer.getLengthBytes(); }
+	int64_t SetRange(int64_t &From, int64_t &To);
+	virtual cString ToText() const;
+	cStreamdevRecStreamer(cRecording *Recording, const cServerConnection *Connection);
+	virtual ~cStreamdevRecStreamer();
+};
+
+#endif // VDR_STREAMDEV_RECSTREAMER_H
diff --git a/server/streamer.c b/server/streamer.c
index 10c267f..ad4dcc5 100644
--- a/server/streamer.c
+++ b/server/streamer.c
@@ -54,6 +54,11 @@ void cStreamdevWriter::Action(void)
 		if (block == NULL) {
 			block = m_Streamer->Get(count);
 			offset = 0;
+			// still no data - are we done?
+			if (block == NULL && !m_Streamer->IsReceiving() && timeout++ > 20) {
+				esyslog("streamdev-server: streamer done - writer exiting");
+				break;
+			}
 		}
 
 		if (block != NULL) {
@@ -100,6 +105,7 @@ void cStreamdevWriter::Action(void)
 			}
 		}
 	}
+	m_Socket->Close();
 	Dprintf("Max. Transmit Blocksize was: %d\n", max);
 }
 
@@ -109,47 +115,38 @@ cStreamdevStreamer::cStreamdevStreamer(const char *Name, const cServerConnection
 		cThread(Name),
 		m_Connection(Connection),
 		m_Writer(NULL),
-		m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2,
-		             true, "streamdev-streamer")),
 		m_SendBuffer(new cStreamdevBuffer(WRITERBUFSIZE, TS_SIZE * 2))
 {
-	m_RingBuffer->SetTimeouts(0, 100);
 	m_SendBuffer->SetTimeouts(100, 100);
 }
 
 cStreamdevStreamer::~cStreamdevStreamer() 
 {
 	Dprintf("Desctructing streamer\n");
-	delete m_RingBuffer;
 	delete m_SendBuffer;
 }
 
 void cStreamdevStreamer::Start(cTBSocket *Socket) 
 {
-	Dprintf("start streamer\n");
+	Dprintf("start writer\n");
 	m_Writer = new cStreamdevWriter(Socket, this);
-	Attach();
-}
-
-void cStreamdevStreamer::Activate(bool On) 
-{
-	if (On && !Active()) {
-		Dprintf("activate streamer\n");
-		m_Writer->Start();
+	m_Writer->Start();
+	if (!Active()) {
+		Dprintf("start streamer\n");
 		cThread::Start();
 	}
+	Attach();
 }
 
 void cStreamdevStreamer::Stop(void) 
 {
+	Detach();
 	if (Running()) {
-		Dprintf("stopping streamer\n");
+		Dprintf("stop streamer\n");
 		Cancel(3);
 	}
-	if (m_Writer) {
-		Detach();
-		DELETENULL(m_Writer);
-	}
+	Dprintf("stop writer\n");
+	DELETENULL(m_Writer);
 }
 
 void cStreamdevStreamer::Action(void) 
@@ -157,12 +154,12 @@ void cStreamdevStreamer::Action(void)
 	SetPriority(-3);
 	while (Running()) {
 		int got;
-		uchar *block = m_RingBuffer->Get(got);
+		uchar *block = GetFromReceiver(got);
 
 		if (block) {
 			int count = Put(block, got);
 			if (count)
-				m_RingBuffer->Del(count);
+				DelFromReceiver(count);
 		}
 	}
 }
diff --git a/server/streamer.h b/server/streamer.h
index c34db54..d9b2998 100644
--- a/server/streamer.h
+++ b/server/streamer.h
@@ -17,7 +17,6 @@ class cServerConnection;
 #define TS_SIZE 188
 #endif
 
-#define STREAMERBUFSIZE (20000 * TS_SIZE)
 #define WRITERBUFSIZE (20000 * TS_SIZE)
 
 // --- cStreamdevBuffer -------------------------------------------------------
@@ -67,10 +66,12 @@ class cStreamdevStreamer: public cThread {
 private:
 	const cServerConnection *m_Connection;
 	cStreamdevWriter  *m_Writer;
-	cStreamdevBuffer  *m_RingBuffer;
 	cStreamdevBuffer  *m_SendBuffer;
 
 protected:
+	virtual uchar* GetFromReceiver(int &Count) = 0;
+	virtual void DelFromReceiver(int Count) = 0;
+	virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->PutTS(Data, Count); }
 	virtual void Action(void);
 
 	bool IsRunning(void) const { return m_Writer; }
@@ -83,18 +84,16 @@ public:
 
 	virtual void Start(cTBSocket *Socket);
 	virtual void Stop(void);
+	virtual bool IsReceiving(void) const = 0;
 	bool Abort(void);
 
-	void Activate(bool On);
-	int Receive(uchar *Data, int Length) { return m_RingBuffer->PutTS(Data, Length); }
-	void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); }
-	
-	virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->PutTS(Data, Count); }
 	virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); }
 	virtual void Del(int Count) { m_SendBuffer->Del(Count); }
 
 	virtual void Detach(void) {}
 	virtual void Attach(void) {}
+
+	virtual cString ToText() const { return ""; };
 };
 
 inline bool cStreamdevStreamer::Abort(void)
diff --git a/tools/socket.h b/tools/socket.h
index effdef8..293e8b2 100644
--- a/tools/socket.h
+++ b/tools/socket.h
@@ -62,7 +62,7 @@ public:
 	/* Accept() returns a newly created cTBSocket, which is connected to the 
 	   first connection request on the queue of pending connections of a
 	   listening socket. If no connection request was pending, or if any other
-	   error occured, the resulting cTBSocket is closed. */
+	   error occurred, the resulting cTBSocket is closed. */
 	virtual cTBSocket Accept(void) const;
 
 	/* Accept() extracts the first connection request on the queue of pending
diff --git a/tools/source.h b/tools/source.h
index 09c4bf3..b6fc36c 100644
--- a/tools/source.h
+++ b/tools/source.h
@@ -54,7 +54,7 @@ public:
 	/* Close() resets the source to the uninitialized state (IsOpen() == false)
 	   and must be called by any derivations after really closing the source.
 	   Returns true on success and false on error, setting errno appropriately.
-	   The object is in closed state afterwards, even if an error occured. */
+	   The object is in closed state afterwards, even if an error occurred. */
 	virtual bool Close(void);
 
 	/* Read() reads at most Length bytes into the storage pointed to by Buffer,

-- 
vdr-plugin-streamdev packaging repository



More information about the pkg-vdr-dvb-changes mailing list