Streamdev 0.3.4

This commit is contained in:
Frank Schmirler 2010-12-02 09:02:31 +01:00
parent 7576173547
commit 31df0eaf8e
21 changed files with 1064 additions and 194 deletions

View File

@ -1,8 +1,11 @@
Special thanks go to the following persons (if you think your name is missing Special thanks go to the following persons (if you think your name is missing
here, please send an email to sascha@akv-soft.de): here, please send an email to vdrdev@schmirler.de):
Sascha Volkenandt, the original author,
for this great plugin
The Metzler Brothers The Metzler Brothers
because I took a whole lot of code from their libdvbmpeg package as a lot of code has been taken from their libdvbmpeg package
Angelus (DOm) Angelus (DOm)
for providing italian language texts for providing italian language texts
@ -13,9 +16,57 @@ Michal
Rolf Ahrenberg Rolf Ahrenberg
for providing finnish language texts for providing finnish language texts
for adding externremux.sh commandline parameter
for silencing compiler warnings
for adding PAT, PMT, PCR and EIT to HTTP TS streams
for fixing a memory leak in buffer overflow situations
for adding a return code check to vasprintf()
for suggesting a fix of the Makefile's default target
for a TS PAT repacker based on Petri Laine's VDR TS recording patch
for making it possible to pass parameters to externremux.sh
Rantanen Teemu Rantanen Teemu
for providing vdr-incompletesections.diff for providing vdr-incompletesections.diff
Thomas Keil Thomas Keil
for providing vdr-localchannelprovide.diff for providing vdr-localchannelprovide.diff
for maintaining the plugin for a while
Artur Skawina
for fixing an fd leak
Norad
for reporting a problem terminated externremux.sh children
Udo Richter
for fixing streamdev-server shutdown
for speeding up cPluginStreamdevServer::Active()
for adapting to VDR 1.5.0 API
greenman
for reporting that the log could get flooded on connection failures.
Petri Hintukainen
for making section filtering work
for fixing a segfault with VDR 1.5
for fixing high CPU load when data stream is disconnected
for adding PAT, PMT and PCR to HTTP TS streams
for fixing a segfault / deadlock when shutting down
for fixing compiler warnings
for adding M3U playlists
ollo
for suggesting support for WMM capable WLAN accesspoints
vdr-freak
for reporting connection aborts when externremux ringbuffer is full
alexw
for reporting client reconnect problems after a server restart
for a workaround for tuning problems with 1.5.x clients
Olli Lammi
for fixing a busy wait when client isn't accepting data fast enough
Joerg Pulz
for his FreeBSD compatibility patch

88
HISTORY
View File

@ -1,6 +1,94 @@
VDR Plugin 'streamdev' Revision History VDR Plugin 'streamdev' Revision History
--------------------------------------- ---------------------------------------
2008-03-31: Version 0.3.4
- added possibility to pass parameter to externremux.sh (thanks to Rolf
Ahrenberg)
- use HTTP host header in absolute URLs for DNAT / reverse proxy support
- rewrite of the HTTP menu part
- added M3U playlists (thanks to Petri Hinutkainen)
- enable section filtering only with compatible clients (thanks to Petri
Hintukainen)
- fixed compiler warning
- added EIT to HTTP TS streams (thanks to Rolf Ahrenberg)
- compatibility for FreeBSD (thanks to Joerg Pulz)
- added TS PAT repacker (thanks to Rolf Ahrenberg)
- fixed Makefile's default target (suggested by Rolf Ahrenberg)
- workaround for tuning problems on 1.5.x clients (thanks to alexw)
- added VTP support for PS, PES and EXTERN (PS requested by mpanczyk)
- fixed gcc-4.3.0 warnings (thanks to Petri Hintukainen)
- fixed busy wait when client isn't accepting data fast enough (thanks to
Olli Lammi)
- fixed client reconnect after server restart (reported by alexw)
- added lock in ~cStreamdevDevice (thanks to Petri Hintukainen)
- externremux: check for ringbuffer full condition (reported by
vdr-freak@vdrportal)
- diffserv support for traffic shaping and WMM capable WLAN accesspoint
(suggested by ollo@vdrportal)
- check vasprintf() return code (thanks to Rolf Ahrenberg)
- fixed memory leak in buffer overflow situations (thanks to Rolf Ahrenberg)
- added PAT, PMT and PCR to HTTP TS streams (thanks to Petri Hintukainen and
Rolf Ahrenberg)
- detect data stream disconnections. Fixes high CPU load (thanks to Petri
Hintukainen)
- fixed segfault with VDR 1.5 (thanks to Petri Hintukainen)
- made section filtering work (thanks to Petri Hintukainen)
- added compiler flag -Wall and fixed corresponding warnings (thanks to
Rolf Ahrenberg)
- close pipe when externremux is gone. Fixes high CPU load problem
- close connection when client is gone. Fixes high CPU load problem
- silenced compiler warnings (thanks to Rolf Ahrenberg)
- added commandline parameter for externremux script (thanks to Rolf
Ahrenberg)
- detach receivers before switching transponders
- API changes for VDR 1.5.0 (thanks to Udo Richter)
- log connections failures only every 10s (reported by greenman@vdrportal)
- replaced uint64 by uint64_t
- added Recursion patch for vdr 1.4
- added LocalChannelProvide for vdr 1.4.x
- added respect_ca patch
- speedup cPluginStreamdevServer::Active() by caching translation (thanks
to Udo Richter)
- periodically check if streamdev-server needs to shutdown (thanks to Udo
Richter)
- collect terminated externremux.sh processes (reported by Norad@vdrportal)
- avoid fd leaks when we fail to spawn externremux.sh
- detach all receivers before tuning to a different transponder
- Re-enabled logging for the Detach()/Attach() issue
- Added -fPIC compiler flag required on AMD64 architectures
2006-08-17: End of maintenance by Thomas Keil
- updated Finish translation (thanks to Rolf Ahrenberg)
- fixed fd leak (thanks to Artur Skawina)
- re-enabled Detach/Attach to temporarily release the device used by
streamdev while checking if we can switch transponders (thanks to
PanamaJack@vdrportal)
- adopted to VDR 1.4.x
2006-01-26: End of maintenance by Sascha Volkenandt
- fixed http error response
- added class forward declaration for gcc >= 4.0
- adopted to VDR >= 1.3.36
- added LocalChannelProvide for vdr 1.3.24
- fixed missing include
- added TS compatibility mode
- deleting whole block instead of fractions now
- fixed wrong remux usage
- added finish translations (thanks to Rolf Ahrenberg)
- protected cStreamer::Stop() from being called concurrently
- some compilers complained about missing declarations, added <ctype.h>
- removed assembler and thus saving one ringbuffer
- fixed destruction order on channel switch (fixes one crash that happens
occasionally when switching)
- removed client menu code temporarily
- streamer now gets stopped when connection terminates unexpectedly
- fixed recursive delete in streamer
- fixed pure virtual crash in server
- audio track selection for http
2004-??-??: Version 0.3.3 2004-??-??: Version 0.3.3
- dropped support for non-ts streaming in vdr-to-vdr clients - dropped support for non-ts streaming in vdr-to-vdr clients

View File

@ -1,7 +1,7 @@
# #
# Makefile for a Video Disk Recorder plugin # Makefile for a Video Disk Recorder plugin
# #
# $Id: Makefile,v 1.8 2007/04/16 11:01:02 schmirl Exp $ # $Id: Makefile,v 1.12 2008/03/31 10:34:26 schmirl Exp $
# The official name of this plugin. # The official name of this plugin.
# This name will be used in the '-P...' option of VDR to load the plugin. # This name will be used in the '-P...' option of VDR to load the plugin.
@ -61,7 +61,7 @@ SERVEROBJS = $(PLUGIN)-server.o \
server/server.o server/connectionVTP.o server/connectionHTTP.o \ server/server.o server/connectionVTP.o server/connectionHTTP.o \
server/componentHTTP.o server/componentVTP.o server/connection.o \ server/componentHTTP.o server/componentVTP.o server/connection.o \
server/component.o server/suspend.o server/setup.o server/streamer.o \ server/component.o server/suspend.o server/setup.o server/streamer.o \
server/livestreamer.o server/livefilter.o \ server/livestreamer.o server/livefilter.o server/menuHTTP.o \
\ \
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o
@ -85,8 +85,10 @@ ifeq ($(shell test -f $(VDRDIR)/sections.c ; echo $$?),0)
DEFINES += -DHAVE_AUTOPID DEFINES += -DHAVE_AUTOPID
endif endif
libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.cc libdvbmpeg/*.h libdvbmpeg/*.hh ### The main target:
make -C ./libdvbmpeg libdvbmpegtools.a
.PHONY: all dist clean
all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so
### Implicit rules: ### Implicit rules:
@ -113,7 +115,9 @@ endif
### Targets: ### Targets:
all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.cc libdvbmpeg/*.h libdvbmpeg/*.hh
$(MAKE) -C ./libdvbmpeg libdvbmpegtools.a
libvdr-$(PLUGIN)-client.so: $(CLIENTOBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a libvdr-$(PLUGIN)-client.so: $(CLIENTOBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
@ -126,10 +130,10 @@ dist: clean
@-rm -rf $(TMPDIR)/$(ARCHIVE) @-rm -rf $(TMPDIR)/$(ARCHIVE)
@mkdir $(TMPDIR)/$(ARCHIVE) @mkdir $(TMPDIR)/$(ARCHIVE)
@cp -a * $(TMPDIR)/$(ARCHIVE) @cp -a * $(TMPDIR)/$(ARCHIVE)
@tar czf $(PACKAGE).tgz --exclude SCCS -C $(TMPDIR) $(ARCHIVE) @tar czf $(PACKAGE).tgz --exclude CVS -C $(TMPDIR) $(ARCHIVE)
@-rm -rf $(TMPDIR)/$(ARCHIVE) @-rm -rf $(TMPDIR)/$(ARCHIVE)
@echo Distribution package created as $(PACKAGE).tgz @echo Distribution package created as $(PACKAGE).tgz
clean: clean:
@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~ @-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~
make -C ./libdvbmpeg clean $(MAKE) -C ./libdvbmpeg clean

168
README
View File

@ -1,10 +1,12 @@
This is a "plugin" for the Video Disk Recorder (VDR). This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Sascha Volkenandt <sascha@akv-soft.de> Written by: Sascha Volkenandt <sascha@akv-soft.de>
Current maintainer: Frank Schmirler <vdrdev@schmirler.de>
Project's homepage: http://www.magoa.net/linux/ Project's homepage: http://streamdev.vdr-developer.org/
Former project homepage: http://linux.kompiliert.net/
Latest version available at: http://www.magoa.net/linux/index.php?view=streamdev Latest version available at: http://streamdev.vdr-developer.org/
See the file COPYING for license information. See the file COPYING for license information.
@ -14,16 +16,12 @@ Contents:
1. Description 1. Description
2. Installation 2. Installation
2.1 VDR 1.2.X 2.1 VDR 1.2.X
2.2 VDR 1.3.X 2.2 VDR 1.3.X and above
3. Usage 3. Usage
3.1 Usage VDR-to-VDR server 3.1 Usage HTTP server
3.2 Usage HTTP server 3.2 Usage VDR-to-VDR server
3.3 Usage VDR-to-VDR client 3.3 Usage VDR-to-VDR client
3.4 General Usage Notes 4. Other useful Plugins
4. VDR-to-VDR client notes (PLEASE READ IF YOU HAVE ONE)
4.1 EPG data [OUTDATED]
4.2 Teletext / OSD Teletext
4.3 AnalogTV [OUTDATED]
5. Known Problems 5. Known Problems
@ -102,8 +100,8 @@ patch -p1 <PLUGINS/src/streamdev/patches/thread.c.diff
make [options, if necessary] vdr make [options, if necessary] vdr
make [options, if necessary] plugins make [options, if necessary] plugins
2.2 VDR 1.3.X: 2.2 VDR 1.3.X and above:
-------------- ------------------------
cd vdr-1.X.X/PLUGINS/src cd vdr-1.X.X/PLUGINS/src
tar xvfz vdr-streamdev-0.3.1.tgz tar xvfz vdr-streamdev-0.3.1.tgz
@ -134,23 +132,13 @@ prevents the client from switching transponders. If you set "Client may
suspend" to yes, the client can suspend the server remotely (this only applies suspend" to yes, the client can suspend the server remotely (this only applies
if "Offer suspend mode" is selected). if "Offer suspend mode" is selected).
3.1 Usage VDR-to-VDR server:
----------------------------
You can activate the VDR-to-VDR server part in the PlugIn's Setup Menu. It is
deactivated by default. The Parameter "VDR-to-VDR Server Port" specifies the
port where you want the server to listen for incoming connections. The server
will be activated when you push the OK button inside the setup menu, so there's
no need to restart VDR.
NOTE: This mainly applies to One-Card-Systems, since with multiple cards there NOTE: This mainly applies to One-Card-Systems, since with multiple cards there
is no need to switch transponders on the primary interface, if the secondary is no need to switch transponders on the primary interface, if the secondary
can stream a given channel (i.e. if it is not blocked by a recording). If both can stream a given channel (i.e. if it is not blocked by a recording). If both
cards are in use (i.e. when something is recorded, or by multiple clients), cards are in use (i.e. when something is recorded, or by multiple clients),
this applies to Multiple-Card-Systems as well. this applies to Multiple-Card-Systems as well.
3.2 Usage HTTP server: 3.1 Usage HTTP server:
---------------------- ----------------------
You can use the HTTP part by accessing the server with a HTTP-capable media You can use the HTTP part by accessing the server with a HTTP-capable media
@ -164,8 +152,17 @@ TS Transport Stream (i.e. a dump from the device)
PES Packetized Elemetary Stream (VDR's native recording format) PES Packetized Elemetary Stream (VDR's native recording format)
PS Program Stream (SVCD, DVD like stream) PS Program Stream (SVCD, DVD like stream)
ES Elementary Stream (only Video, if available, otherwise only Audio) ES Elementary Stream (only Video, if available, otherwise only Audio)
EXTERN Pass stream through external script (e.g. for converting with mencoder)
If you leave the default port (3000), you can access the streams like this: Assuming that you leave the default port (3000), point your web browser to
http://hostname:3000/
You will be presented a menu with links to various channel lists, including M3U
playlist formats.
If you don't want to use the HTML menu or the M3U playlists, you can access the
streams directly like this:
http://hostname:3000/3 http://hostname:3000/3
http://hostname:3000/S19.2E-0-12480-898 http://hostname:3000/S19.2E-0-12480-898
@ -178,12 +175,26 @@ http://hostname:3000/TS/3
http://hostname:3000/PES/S19.2E-0-12480-898 http://hostname:3000/PES/S19.2E-0-12480-898
The first one would deliver the stream in TS, the second one in PES format. The first one would deliver the stream in TS, the second one in PES format.
Possible values are 'PES', 'TS', 'PS' and 'ES'. You need to specify the ES Possible values are 'PES', 'TS', 'PS', 'ES' and 'EXTERN'. You need to specify
format explicitly if you want to listen to radio channels. Play them pack i.e. the ES format explicitly if you want to listen to radio channels. Play them
with mpg123. back i.e. with mpg123.
mpg123 http://hostname:3000/ES/200 mpg123 http://hostname:3000/ES/200
With 'EXTERN' you can also add a parameter which is passed as argument to the
externremux script.
http://hostname:3000/EXTERN;some_parameter/3
3.2 Usage VDR-to-VDR server:
----------------------------
You can activate the VDR-to-VDR server part in the PlugIn's Setup Menu. It is
deactivated by default. The Parameter "VDR-to-VDR Server Port" specifies the
port where you want the server to listen for incoming connections. The server
will be activated when you push the OK button inside the setup menu, so there's
no need to restart VDR.
3.3 Usage VDR-to-VDR client: 3.3 Usage VDR-to-VDR client:
---------------------------- ----------------------------
@ -216,84 +227,71 @@ 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 server needs that card (if no other is free) for a recording on a different
transponder. transponder.
You can choose a remote streamtype in the setup. I'd suggest TS streaming as Only the needed PIDs are transferred, and additional PIDs can be turned on
it has a much shorter delay than PES streaming (compared to live-view of the during an active transfer. This makes it possible to switch languages, receive
same channel on the server), and transmits more information such as AC3 and additional channels (for recording on the client) and use plugins that use
teletext data. receivers themselves (like osdteletext).
When setting the parameter "MultiPID streaming" to yes (the default) (only With "Filter Streaming" enabled, the client will receive meta information like
applies if the streamtype is TS), only the needed PIDs are transferred, and EPG data and service information, just as if the client had its own DVB card.
additional PIDs can be turned on during an active transfer. This makes it Link channels and even a client-side EPG scan have been reported to work.
possible to switch languages, receive additional channels (for recording on
the client) and use plugins that use receivers themselves (like osdteletext).
The last parameter, "Synchronize EPG", will have the client synchronize it's The last parameter, "Synchronize EPG", will have the client synchronize it's
program table with the server every now and then, but not regularly. This program table with the server every now and then, but not regularly. This
happens when starting the client, and everytime VDR does its housekeeping happens when starting the client, and everytime VDR does its housekeeping
tasks. The only thing that's guaranteed is, that there will be a minimum tasks. The only thing that's guaranteed is, that there will be a minimum
interval of ten seconds between each EPG synchronization. interval of ten seconds between each EPG synchronization. With "Filter
Streaming" this option has been obsoleted. If you still need to synchronize
EPG as additional information is available from the server, you should use the
epgsync-plugin instead (http://vdr.schmirler.de).
The client has a Main Menu entry called "Streaming Control". This is used to 4. Other useful Plugins:
control various aspects of the remote server VDR. Inside, you will find
"Remote Timers", "Remote Recordings", "Suspend server" and "Synchronize EPG".
The "Remote Timers" entry gives you the possibility to edit, create and delete
the server's timers remotely. Every timer is synchronized before the requested
action actually takes place. This only leaves a very short time-span (of a few
milliseconds) in which a race-condition could happen.
"Remote Recordings" shows up all recordings that the server can access. Only
deleting recordings is implemented, yet.
With "Suspend Server", you can send the server into suspend mode remotely, if
the server is set to "Offer suspend mode" and allows the client to suspend.
Last but not least, "Synchronize EPG" starts a synchronization in case you
don't want to do it regularly, or in case you just activated it and can't wait
for the first synchronization to happen by itself.
3.4 General Usage Notes:
------------------------ ------------------------
If there's still some debug output on stdout, please ignore it ;) 4.1 Plugins for VDR-to-VDR clients:
-----------------------------------
The following plugins are useful for VDR-to-VDR clients (i.e. VDRs running the
streamdev-client):
4. VDR-to-VDR client notes: * remotetimers (http://vdr.schmirler.de/)
--------------------------- Add, edit, delete timers on client and server
4.1 EPG data: * timersync (http://phivdr.dyndns.org/vdr/vdr-timersync/)
-------------- Automatically syncronizes timer lists of client and server. All recordings will
be made on the server
[ OUTDATED, see "Synchronize EPG" in 3.2 ] * remoteosd (http://vdr.schmirler.de/)
Provides access to the server's OSD menu
4.2 Teletext / OSD Teletext: * epgsync (http://vdr.schmirler.de/)
----------------------------- Import EPG from server VDR
Usual teletext will probably not work on the client, if it has no DVB hardware. * femon (http://www.saunalahti.fi/~rahrenbe/vdr/femon/)
I never tried, and probably I never will, so don't ask about it please ;) Display signal information from server's DVB card. SVDRP support must be enabled
in femon's setup
Osdteletext-0.3.1 (and later) definitely work when used in MultiPID Streaming 4.2 Plugins for Server:
mode. -----------------------
* dummydevice (http://phivdr.dyndns.org/vdr/vdr-dummydevice/)
Recommended on a headless server (i.e. a server with no real output device).
Without this plugin, a budget DVB card could become VDR's primary device. This
causes unwanted sideeffects in certain situations.
4.3 AnalogTV 4.3 Alternatives:
------------ -----------------
Works with ivtv and analogue cards according to Andreas Kool.
* xineliboutput (http://phivdr.dyndns.org/vdr/vdr-xineliboutput/)
With its networking option, xineliboutput provides an alternative to streamdev.
You will get the picture of the server VDR, including its OSD. However you
won't get independent clients, as they all share the same output.
5. Known Problems: 5. Known Problems:
------------------ ------------------
- Recordings & Timers on the client side could endanger Timers & Recordings on * In VDR-to-VDR setup, the availability of a channel is checked with a different
the server, as they will have the same priority (by default). Set the priority than the actual channel switch. The later always uses priority 0.
default priority to i.e. 40 if you want the server to supersede the client. Usually a channel switch for live TV has priority 0 anyway, so it is not a
problem here. However timers usually have a higher priority. Either avoid
- Sometimes, if you reload VDR too often (for example while recompiling), the client side recordings or set the priority of client side timers to 0.
driver can get "stuck" in some situations. Try a driver restart if anything
you think should work doesn't before sending a bug-report :-).
[ ADDITION ]
In the meantime I have discovered that this error is caused by the all-
mysterical UPT (unknown picture type) error :-(.

View File

@ -1,5 +1,5 @@
/* /*
* $Id: device.c,v 1.14 2007/07/20 06:46:47 schmirl Exp $ * $Id: device.c,v 1.15 2007/12/12 12:22:45 schmirl Exp $
*/ */
#include "client/device.h" #include "client/device.h"
@ -212,8 +212,16 @@ void cStreamdevDevice::CloseDvrInt(void) {
} }
Dprintf("cStreamdevDevice::CloseDvrInt(): Closing DVR connection\n"); Dprintf("cStreamdevDevice::CloseDvrInt(): Closing DVR connection\n");
#if VDRVERSNUM < 10500
DELETENULL(m_TSBuffer); DELETENULL(m_TSBuffer);
ClientSocket.CloseDvr(); ClientSocket.CloseDvr();
#else
// Hack for VDR 1.5.x clients (sometimes sending ABRT after TUNE)
// TODO: Find a clean solution to fix this
ClientSocket.SetChannelDevice(m_Channel);
ClientSocket.CloseDvr();
DELETENULL(m_TSBuffer);
#endif
} }
void cStreamdevDevice::CloseDvr(void) { void cStreamdevDevice::CloseDvr(void) {

View File

@ -1,5 +1,5 @@
/* /*
* $Id: socket.c,v 1.8 2007/04/24 10:57:34 schmirl Exp $ * $Id: socket.c,v 1.9 2008/03/13 16:01:17 schmirl Exp $
*/ */
#include <tools/select.h> #include <tools/select.h>
@ -140,8 +140,14 @@ bool cClientSocket::CheckConnection(void) {
return false; return false;
} }
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS", const char *Filters = "";
RemoteIp().c_str(), RemotePort()); #if VDRVERSNUM >= 10300
if(Command("CAPS FILTERS", 220))
Filters = ",FILTERS";
#endif
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s",
RemoteIp().c_str(), RemotePort(), Filters);
return true; return true;
} }

View File

@ -1,5 +1,5 @@
/* /*
* $Id: common.c,v 1.5 2007/09/21 11:55:56 schmirl Exp $ * $Id: common.c,v 1.6 2008/03/31 10:34:26 schmirl Exp $
*/ */
#include <vdr/channels.h> #include <vdr/channels.h>
@ -11,7 +11,7 @@
using namespace std; using namespace std;
const char *VERSION = "0.3.3-20070921"; const char *VERSION = "0.3.4";
const char *StreamTypes[st_Count] = { const char *StreamTypes[st_Count] = {
"TS", "TS",

View File

@ -1,10 +1,16 @@
/* /*
* $Id: common.h,v 1.8 2007/04/24 10:50:13 schmirl Exp $ * $Id: common.h,v 1.9 2008/03/12 09:36:27 schmirl Exp $
*/ */
#ifndef VDR_STREAMDEV_COMMON_H #ifndef VDR_STREAMDEV_COMMON_H
#define VDR_STREAMDEV_COMMON_H #define VDR_STREAMDEV_COMMON_H
/* FreeBSD has it's own version of isnumber(),
but VDR's version is incompatible */
#ifdef __FreeBSD__
#undef isnumber
#endif
#include <vdr/tools.h> #include <vdr/tools.h>
#include <vdr/plugin.h> #include <vdr/plugin.h>

View File

@ -2060,7 +2060,11 @@ void split_mpg(char *name, uint64_t size)
if (break_up_filename(name,base_name,path,ext) < 0) exit(1); if (break_up_filename(name,base_name,path,ext) < 0) exit(1);
#ifdef __FreeBSD__
if ( (fdin = open(name, O_RDONLY)) < 0){
#else
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){ if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
#endif
fprintf(stderr,"Can't open %s\n",name); fprintf(stderr,"Can't open %s\n",name);
exit(1); exit(1);
} }
@ -2101,8 +2105,12 @@ void split_mpg(char *name, uint64_t size)
sprintf(new_name,"%s-%03d.%s",base_name,i,ext); sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
printf("writing %s\n",new_name); printf("writing %s\n",new_name);
#ifdef __FreeBSD__
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
#else
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|O_LARGEFILE, |O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){ S_IROTH|S_IWOTH)) < 0){
fprintf(stderr,"Can't open %s\n",new_name); fprintf(stderr,"Can't open %s\n",new_name);
@ -2114,8 +2122,12 @@ void split_mpg(char *name, uint64_t size)
sprintf(new_name,"%s-%03d.%s",base_name,i,ext); sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
printf("writing %s\n",new_name); printf("writing %s\n",new_name);
#ifdef __FreeBSD__
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
#else
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|O_LARGEFILE, |O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){ S_IROTH|S_IWOTH)) < 0){
fprintf(stderr,"Can't open %s\n",new_name); fprintf(stderr,"Can't open %s\n",new_name);
@ -2144,7 +2156,11 @@ void cut_mpg(char *name, uint64_t size)
if (break_up_filename(name,base_name,path,ext) < 0) exit(1); if (break_up_filename(name,base_name,path,ext) < 0) exit(1);
#ifdef __FreeBSD__
if ( (fdin = open(name, O_RDONLY)) < 0){
#else
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){ if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
#endif
fprintf(stderr,"Can't open %s\n",name); fprintf(stderr,"Can't open %s\n",name);
exit(1); exit(1);
} }
@ -2182,8 +2198,12 @@ void cut_mpg(char *name, uint64_t size)
sprintf(new_name,"%s-1.%s",base_name,ext); sprintf(new_name,"%s-1.%s",base_name,ext);
printf("writing %s\n",new_name); printf("writing %s\n",new_name);
#ifdef __FreeBSD__
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
#else
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|O_LARGEFILE, |O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){ S_IROTH|S_IWOTH)) < 0){
fprintf(stderr,"Can't open %s\n",new_name); fprintf(stderr,"Can't open %s\n",new_name);
@ -2195,8 +2215,12 @@ void cut_mpg(char *name, uint64_t size)
sprintf(new_name,"%s-2.%s",base_name,ext); sprintf(new_name,"%s-2.%s",base_name,ext);
printf("writing %s\n",new_name); printf("writing %s\n",new_name);
#ifdef __FreeBSD__
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
#else
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|O_LARGEFILE, |O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){ S_IROTH|S_IWOTH)) < 0){
fprintf(stderr,"Can't open %s\n",new_name); fprintf(stderr,"Can't open %s\n",new_name);

View File

@ -19,13 +19,13 @@ protected:
virtual void Action(void); virtual void Action(void);
public: public:
cTSExt(cRingBufferLinear *ResultBuffer); cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter);
virtual ~cTSExt(); virtual ~cTSExt();
void Put(const uchar *Data, int Count); void Put(const uchar *Data, int Count);
}; };
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer): cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
m_ResultBuffer(ResultBuffer), m_ResultBuffer(ResultBuffer),
m_Active(false), m_Active(false),
m_Process(0), m_Process(0),
@ -67,9 +67,8 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer):
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++) for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
close(i); //close all dup'ed filedescriptors close(i); //close all dup'ed filedescriptors
//printf("starting externremux.sh\n"); std::string cmd = std::string(g_ExternRemux) + " " + Parameter;
execl("/bin/sh", "sh", "-c", g_ExternRemux, NULL); execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
//printf("failed externremux.sh\n");
_exit(-1); _exit(-1);
} }
@ -150,9 +149,9 @@ void cTSExt::Put(const uchar *Data, int Count)
} }
} }
cExternRemux::cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids): cExternRemux::cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter):
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)), m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)),
m_Remux(new cTSExt(m_ResultBuffer)) m_Remux(new cTSExt(m_ResultBuffer, Parameter))
{ {
m_ResultBuffer->SetTimeouts(500, 100); m_ResultBuffer->SetTimeouts(500, 100);
} }

View File

@ -3,6 +3,7 @@
#include "remux/tsremux.h" #include "remux/tsremux.h"
#include <vdr/ringbuffer.h> #include <vdr/ringbuffer.h>
#include <string>
extern const char *g_ExternRemux; extern const char *g_ExternRemux;
@ -14,7 +15,7 @@ private:
cTSExt *m_Remux; cTSExt *m_Remux;
public: public:
cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids); cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter);
virtual ~cExternRemux(); virtual ~cExternRemux();
int Put(const uchar *Data, int Count); int Put(const uchar *Data, int Count);

View File

@ -1,20 +1,22 @@
/* /*
* $Id: connectionHTTP.c,v 1.12 2007/05/09 09:12:42 schmirl Exp $ * $Id: connectionHTTP.c,v 1.13 2008/03/28 15:11:40 schmirl Exp $
*/ */
#include <ctype.h> #include <ctype.h>
#include "server/connectionHTTP.h" #include "server/connectionHTTP.h"
#include "server/menuHTTP.h"
#include "server/setup.h" #include "server/setup.h"
cConnectionHTTP::cConnectionHTTP(void): cConnectionHTTP::cConnectionHTTP(void):
cServerConnection("HTTP"), cServerConnection("HTTP"),
m_Status(hsRequest), m_Status(hsRequest),
m_LiveStreamer(NULL), m_LiveStreamer(NULL),
m_StreamerParameter(""),
m_Channel(NULL), m_Channel(NULL),
m_Apid(0), m_Apid(0),
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType), m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
m_ListChannel(NULL) m_ChannelList(NULL)
{ {
Dprintf("constructor hsRequest\n"); Dprintf("constructor hsRequest\n");
} }
@ -39,6 +41,10 @@ bool cConnectionHTTP::Command(char *Cmd)
m_Status = hsBody; m_Status = hsBody;
return ProcessRequest(); return ProcessRequest();
} }
if (strncasecmp(Cmd, "Host:", 5) == 0) {
Dprintf("Host-Header\n");
m_Host = (std::string) skipspace(Cmd + 5);
}
Dprintf("header\n"); Dprintf("header\n");
return true; return true;
default: default:
@ -53,11 +59,9 @@ bool cConnectionHTTP::ProcessRequest(void)
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) { if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
switch (m_Job) { switch (m_Job) {
case hjListing: case hjListing:
return Respond("HTTP/1.0 200 OK") if (m_ChannelList)
&& Respond("Content-Type: text/html") return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
&& Respond("") break;
&& Respond("<html><head><title>VDR Channel Listing</title></head>")
&& Respond("<body><ul>");
case hjTransfer: case hjTransfer:
if (m_Channel == NULL) { if (m_Channel == NULL) {
@ -65,7 +69,7 @@ bool cConnectionHTTP::ProcessRequest(void)
return Respond("HTTP/1.0 404 not found"); return Respond("HTTP/1.0 404 not found");
} }
m_LiveStreamer = new cStreamdevLiveStreamer(0); m_LiveStreamer = new cStreamdevLiveStreamer(0, m_StreamerParameter);
cDevice *device = GetDevice(m_Channel, 0); cDevice *device = GetDevice(m_Channel, 0);
if (device != NULL) { if (device != NULL) {
device->SwitchChannel(m_Channel, false); device->SwitchChannel(m_Channel, false);
@ -106,43 +110,21 @@ void cConnectionHTTP::Flushed(void)
switch (m_Job) { switch (m_Job) {
case hjListing: case hjListing:
if (m_ListChannel == NULL) { if (m_ChannelList) {
Respond("</ul></body></html>"); if (m_ChannelList->HasNext()) {
if (!Respond("%s", true, m_ChannelList->Next().c_str()))
DeferClose(); DeferClose();
}
else {
DELETENULL(m_ChannelList);
m_Status = hsFinished; m_Status = hsFinished;
DeferClose();
}
return; return;
} }
// should never be reached
if (m_ListChannel->GroupSep()) esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list");
line = (std::string)"<li>--- " + m_ListChannel->Name() + "---</li>"; m_Status = hsFinished;
else {
int index = 1;
line = (std::string)"<li><a href=\"http://" + LocalIp() + ":"
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
+ StreamTypes[m_StreamType] + "/"
+ (const char*)m_ListChannel->GetChannelID().ToString() + "\">"
+ m_ListChannel->Name() + "</a> ";
for (int i = 0; m_ListChannel->Apid(i) != 0; ++i, ++index) {
line += "<a href=\"http://" + LocalIp() + ":"
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
+ StreamTypes[m_StreamType] + "/"
+ (const char*)m_ListChannel->GetChannelID().ToString() + "+"
+ (const char*)itoa(index) + "\">("
+ m_ListChannel->Alang(i) + ")</a> ";
}
for (int i = 0; m_ListChannel->Dpid(i) != 0; ++i, ++index) {
line += "<a href=\"http://" + LocalIp() + ":"
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
+ StreamTypes[m_StreamType] + "/"
+ (const char*)m_ListChannel->GetChannelID().ToString() + "+"
+ (const char*)itoa(index) + "\">("
+ m_ListChannel->Dlang(i) + ")</a> ";
}
line += "</li>";
}
if (!Respond(line.c_str()))
DeferClose();
m_ListChannel = Channels.Next(m_ListChannel);
break; break;
case hjTransfer: case hjTransfer:
@ -155,49 +137,131 @@ void cConnectionHTTP::Flushed(void)
bool cConnectionHTTP::CmdGET(const std::string &Opts) bool cConnectionHTTP::CmdGET(const std::string &Opts)
{ {
const char *sp = Opts.c_str(), *ptr = sp, *ep; const char *ptr, *sp, *pp, *fp, *xp, *qp, *ep;
const cChannel *chan; const cChannel *chan;
int apid = 0; int apid = 0;
ptr = skipspace(ptr); ptr = Opts.c_str();
while (*ptr == '/')
++ptr;
if (strncasecmp(ptr, "PS/", 3) == 0) { // find begin of URL
sp = skipspace(ptr);
// find end of URL (\0 or first space character)
for (ep = sp; *ep && !isspace(*ep); ep++)
;
// find begin of query string (first ?)
for (qp = sp; qp < ep && *qp != '?'; qp++)
;
// find begin of filename (last /)
for (fp = qp; fp > sp && *fp != '/'; --fp)
;
// find begin of section params (first ;)
for (pp = sp; pp < fp && *pp != ';'; pp++)
;
// find filename extension (first .)
for (xp = fp; xp < qp && *xp != '.'; xp++)
;
if (qp - xp > 5) // too long for a filename extension
xp = qp;
std::string type, filespec, fileext, query;
// Streamtype with leading / stripped off
if (pp > sp)
type = Opts.substr(sp - ptr + 1, pp - sp - 1);
// Section parameters with leading ; stripped off
if (fp > pp)
m_StreamerParameter = Opts.substr(pp - ptr + 1, fp - pp - 1);
// file basename with leading / stripped off
if (xp > fp)
filespec = Opts.substr(fp - ptr + 1, xp - fp - 1);
// file extension including leading .
fileext = Opts.substr(xp - ptr, qp - xp);
// query string including leading ?
query = Opts.substr(qp - ptr, ep - qp);
Dprintf("before channelfromstring: type(%s) param(%s) filespec(%s) fileext(%s) query(%s)\n", type.c_str(), m_StreamerParameter.c_str(), filespec.c_str(), fileext.c_str(), query.c_str());
const char* pType = type.c_str();
if (strcasecmp(pType, "PS") == 0) {
m_StreamType = stPS; m_StreamType = stPS;
ptr += 3; } else if (strcasecmp(pType, "PES") == 0) {
} else if (strncasecmp(ptr, "PES/", 4) == 0) {
m_StreamType = stPES; m_StreamType = stPES;
ptr += 4; } else if (strcasecmp(pType, "TS") == 0) {
} else if (strncasecmp(ptr, "TS/", 3) == 0) {
m_StreamType = stTS; m_StreamType = stTS;
ptr += 3; } else if (strcasecmp(pType, "ES") == 0) {
} else if (strncasecmp(ptr, "ES/", 3) == 0) {
m_StreamType = stES; m_StreamType = stES;
ptr += 3; } else if (strcasecmp(pType, "Extern") == 0) {
} else if (strncasecmp(ptr, "Extern/", 3) == 0) {
m_StreamType = stExtern; m_StreamType = stExtern;
ptr += 7;
} }
while (*ptr == '/') std::string groupTarget;
++ptr; cChannelIterator *iterator = NULL;
for (ep = ptr + strlen(ptr); ep >= ptr && !isspace(*ep); --ep)
;
std::string filespec = Opts.substr(ptr - sp, ep - ptr); if (filespec.compare("tree") == 0) {
Dprintf("substr: %s\n", filespec.c_str()); 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);
groupTarget = filespec + fileext;
} else if (filespec.compare("groups") == 0) {
iterator = new cListGroups();
groupTarget = (std::string) "group" + fileext;
} else if (filespec.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);
} else if (filespec.compare("channels") == 0) {
iterator = new cListChannels();
} else if (filespec.compare("all") == 0 ||
(filespec.empty() && fileext.empty())) {
iterator = new cListAll();
}
Dprintf("before channelfromstring\n"); if (iterator) {
if (filespec == "" || filespec.substr(0, 12) == "channels.htm") { if (filespec.empty() || fileext.compare(".htm") == 0 || fileext.compare(".html") == 0) {
m_ListChannel = Channels.First(); m_ChannelList = new cHtmlChannelList(iterator, m_StreamType, (filespec + fileext + query).c_str(), groupTarget.c_str());
m_Job = hjListing; m_Job = hjListing;
} else if (fileext.compare(".m3u") == 0) {
std::string base;
if (*(m_Host.c_str()))
base = "http://" + m_Host + "/";
else
base = (std::string) "http://" + LocalIp() + ":" +
(const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/";
if (type.empty())
{
switch (m_StreamType)
{
case stTS: base += "TS/"; break;
case stPS: base += "PS/"; break;
case stPES: base += "PES/"; break;
case stES: base += "ES/"; break;
case stExtern: base += "Extern/"; break;
default: break;
}
} else {
base += type;
if (!m_StreamerParameter.empty())
base += ";" + m_StreamerParameter;
base += "/";
}
m_ChannelList = new cM3uChannelList(iterator, base.c_str());
m_Job = hjListing;
} else {
delete iterator;
return false;
}
} else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) { } else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) {
m_Channel = chan; m_Channel = chan;
m_Apid = apid; m_Apid = apid;
Dprintf("Apid is %d\n", apid); Dprintf("Apid is %d\n", apid);
m_Job = hjTransfer; m_Job = hjTransfer;
} } else
return false;
Dprintf("after channelfromstring\n"); Dprintf("after channelfromstring\n");
return true; return true;
} }

View File

@ -1,5 +1,5 @@
/* /*
* $Id: connectionHTTP.h,v 1.4 2007/04/02 10:32:34 schmirl Exp $ * $Id: connectionHTTP.h,v 1.5 2008/03/28 15:11:40 schmirl Exp $
*/ */
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H #ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
@ -12,6 +12,7 @@
class cChannel; class cChannel;
class cStreamdevLiveStreamer; class cStreamdevLiveStreamer;
class cChannelList;
class cConnectionHTTP: public cServerConnection { class cConnectionHTTP: public cServerConnection {
private: private:
@ -28,16 +29,18 @@ private:
}; };
std::string m_Request; std::string m_Request;
std::string m_Host;
//std::map<std::string,std::string> m_Headers; TODO: later? //std::map<std::string,std::string> m_Headers; TODO: later?
eHTTPStatus m_Status; eHTTPStatus m_Status;
eHTTPJob m_Job; eHTTPJob m_Job;
// job: transfer // job: transfer
cStreamdevLiveStreamer *m_LiveStreamer; cStreamdevLiveStreamer *m_LiveStreamer;
std::string m_StreamerParameter;
const cChannel *m_Channel; const cChannel *m_Channel;
int m_Apid; int m_Apid;
eStreamType m_StreamType; eStreamType m_StreamType;
// job: listing // job: listing
const cChannel *m_ListChannel; cChannelList *m_ChannelList;
protected: protected:
bool ProcessRequest(void); bool ProcessRequest(void);

View File

@ -1,5 +1,5 @@
/* /*
* $Id: connectionVTP.c,v 1.15 2007/09/21 12:45:31 schmirl Exp $ * $Id: connectionVTP.c,v 1.17 2008/03/13 16:01:18 schmirl Exp $
*/ */
#include "server/connectionVTP.h" #include "server/connectionVTP.h"
@ -186,7 +186,11 @@ bool cLSTEHandler::Next(bool &Last)
case Event: case Event:
if (m_Event != NULL) { if (m_Event != NULL) {
m_State = Title; m_State = Title;
#ifdef __FreeBSD__
return m_Client->Respond(-215, "E %u %d %d %X", m_Event->EventID(),
#else
return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(), return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(),
#endif
m_Event->StartTime(), m_Event->Duration(), m_Event->StartTime(), m_Event->Duration(),
m_Event->TableID()); m_Event->TableID());
} else { } else {
@ -225,7 +229,11 @@ bool cLSTEHandler::Next(bool &Last)
case Vps: case Vps:
m_State = EndEvent; m_State = EndEvent;
if (m_Event->Vps()) if (m_Event->Vps())
#ifdef __FreeBSD__
return m_Client->Respond(-215, "V %d", m_Event->Vps());
#else
return m_Client->Respond(-215, "V %ld", m_Event->Vps()); return m_Client->Respond(-215, "V %ld", m_Event->Vps());
#endif
else else
return Next(Last); return Next(Last);
break; break;
@ -470,6 +478,7 @@ cConnectionVTP::cConnectionVTP(void):
m_FilterStreamer(NULL), m_FilterStreamer(NULL),
m_LastCommand(NULL), m_LastCommand(NULL),
m_StreamType(stTSPIDS), m_StreamType(stTSPIDS),
m_FiltersSupport(false),
m_LSTEHandler(NULL), m_LSTEHandler(NULL),
m_LSTCHandler(NULL), m_LSTCHandler(NULL),
m_LSTTHandler(NULL) m_LSTTHandler(NULL)
@ -600,8 +609,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
// //
// Deliver section filters data in separate, channel-independent data stream // Deliver section filters data in separate, channel-independent data stream
// //
if (strcasecmp(Opts, "FILTERS") == 0) if (strcasecmp(Opts, "FILTERS") == 0) {
m_FiltersSupport = true;
return Respond(220, "Capability \"%s\" accepted", Opts); return Respond(220, "Capability \"%s\" accepted", Opts);
}
#endif #endif
return Respond(561, "Capability \"%s\" not known", Opts); return Respond(561, "Capability \"%s\" not known", Opts);
@ -672,6 +683,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
#if VDRVERSNUM >= 10300 #if VDRVERSNUM >= 10300
if (id == siLiveFilter) { if (id == siLiveFilter) {
m_FiltersSupport = true;
if(m_FilterStreamer) if(m_FilterStreamer)
m_FilterStreamer->Stop(); m_FilterStreamer->Stop();
delete m_FilterSocket; delete m_FilterSocket;
@ -735,10 +747,12 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
m_LiveStreamer->Start(m_LiveSocket); m_LiveStreamer->Start(m_LiveSocket);
#if VDRVERSNUM >= 10300 #if VDRVERSNUM >= 10300
if(m_FiltersSupport) {
if(!m_FilterStreamer) if(!m_FilterStreamer)
m_FilterStreamer = new cStreamdevFilterStreamer; m_FilterStreamer = new cStreamdevFilterStreamer;
m_FilterStreamer->SetDevice(dev); m_FilterStreamer->SetDevice(dev);
//m_FilterStreamer->SetChannel(chan); //m_FilterStreamer->SetChannel(chan);
}
#endif #endif
return Respond(220, "Channel tuned"); return Respond(220, "Channel tuned");

View File

@ -24,6 +24,7 @@ private:
char *m_LastCommand; char *m_LastCommand;
eStreamType m_StreamType; eStreamType m_StreamType;
bool m_FiltersSupport;
// Members adopted for SVDRP // Members adopted for SVDRP
cRecordings Recordings; cRecordings Recordings;

View File

@ -12,6 +12,8 @@
#include "remux/extern.h" #include "remux/extern.h"
#include "common.h" #include "common.h"
#define TSPATREPACKER
// --- cStreamdevLiveReceiver ------------------------------------------------- // --- cStreamdevLiveReceiver -------------------------------------------------
class cStreamdevLiveReceiver: public cReceiver { class cStreamdevLiveReceiver: public cReceiver {
@ -232,9 +234,48 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
pmtSid = assoc.getServiceId(); pmtSid = assoc.getServiceId();
if (Length < TS_SIZE-5) { if (Length < TS_SIZE-5) {
// repack PAT to TS frame and send to client // repack PAT to TS frame and send to client
#ifndef TSPATREPACKER
uint8_t pat_ts[TS_SIZE] = {TS_SYNC_BYTE, 0x40 /* pusi=1 */, 0 /* pid=0 */, 0x10 /* adaption=1 */, 0 /* pointer */}; uint8_t pat_ts[TS_SIZE] = {TS_SYNC_BYTE, 0x40 /* pusi=1 */, 0 /* pid=0 */, 0x10 /* adaption=1 */, 0 /* pointer */};
memcpy(pat_ts + 5, Data, Length); memcpy(pat_ts + 5, Data, Length);
m_Streamer->Put(pat_ts, TS_SIZE); m_Streamer->Put(pat_ts, TS_SIZE);
#else
int ts_id;
unsigned int crc, i, len;
uint8_t *tmp, tspat_buf[TS_SIZE];
memset(tspat_buf, 0xff, TS_SIZE);
memset(tspat_buf, 0x0, 4 + 12 + 5); // TS_HDR_LEN + PAT_TABLE_LEN + 5
ts_id = Channel->Tid(); // Get transport stream id of the channel
tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h)
tspat_buf[1] = 0x40; // Set payload unit start indicator bit
tspat_buf[2] = 0x0; // PID
tspat_buf[3] = 0x10; // Set payload flag to indicate precence of payload data
tspat_buf[4] = 0x0; // PSI
tspat_buf[5] = 0x0; // PAT table id
tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set
tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1
tspat_buf[8] = (ts_id >> 8) & 0xff; // Transport stream ID (bits 8-15)
tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7)
tspat_buf[10] = 0x01; // Version number 0, Current next indicator bit set
tspat_buf[11] = 0x0; // Section number
tspat_buf[12] = 0x0; // Last section number
tspat_buf[13] = (pmtSid >> 8) & 0xff; // Program number (bits 8-15)
tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7)
tspat_buf[15] = (pmtPid >> 8) & 0xff; // Network ID (bits 8-12)
tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7)
crc = 0xffffffff;
len = 12; // PAT_TABLE_LEN
tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1
while (len--) {
crc ^= *tmp++ << 24;
for (i = 0; i < 8; i++)
crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY
}
tspat_buf[17] = crc >> 24 & 0xff; // Checksum
tspat_buf[18] = crc >> 16 & 0xff; // Checksum
tspat_buf[19] = crc >> 8 & 0xff; // Checksum
tspat_buf[20] = crc & 0xff; // Checksum
m_Streamer->Put(tspat_buf, TS_SIZE);
#endif
} else } else
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length); isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
m_Streamer->SetPids(pmtPid); m_Streamer->SetPids(pmtPid);
@ -268,9 +309,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
#if 0 #if 0
pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT
pids[npids++] = 0x11; // pid 0x11, tid 0x42: SDT pids[npids++] = 0x11; // pid 0x11, tid 0x42: SDT
pids[npids++] = 0x12; // pid 0x12, tid 0x4E...0x6F: EIT
pids[npids++] = 0x14; // pid 0x14, tid 0x70: TDT pids[npids++] = 0x14; // pid 0x14, tid 0x70: TDT
#endif #endif
pids[npids++] = 0x12; // pid 0x12, tid 0x4E...0x6F: EIT
for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); ) for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); )
if (0 != (pids[npids] = GetPid(stream)) && npids < MAXRECEIVEPIDS) if (0 != (pids[npids] = GetPid(stream)) && npids < MAXRECEIVEPIDS)
npids++; npids++;
@ -282,9 +323,10 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
// --- cStreamdevLiveStreamer ------------------------------------------------- // --- cStreamdevLiveStreamer -------------------------------------------------
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority): cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter):
cStreamdevStreamer("streamdev-livestreaming"), cStreamdevStreamer("streamdev-livestreaming"),
m_Priority(Priority), m_Priority(Priority),
m_Parameter(Parameter),
m_NumPids(0), m_NumPids(0),
m_StreamType(stTSPIDS), m_StreamType(stTSPIDS),
m_Channel(NULL), m_Channel(NULL),
@ -447,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
case stExtern: case stExtern:
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids()); m_Channel->Spids(), m_Parameter);
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
case stTSPIDS: case stTSPIDS:

View File

@ -19,6 +19,7 @@ class cStreamdevLiveReceiver;
class cStreamdevLiveStreamer: public cStreamdevStreamer { class cStreamdevLiveStreamer: public cStreamdevStreamer {
private: private:
int m_Priority; int m_Priority;
std::string m_Parameter;
int m_Pids[MAXRECEIVEPIDS + 1]; int m_Pids[MAXRECEIVEPIDS + 1];
int m_NumPids; int m_NumPids;
eStreamType m_StreamType; eStreamType m_StreamType;
@ -35,7 +36,7 @@ private:
bool HasPid(int Pid); bool HasPid(int Pid);
public: public:
cStreamdevLiveStreamer(int Priority); cStreamdevLiveStreamer(int Priority, std::string Parameter = "");
virtual ~cStreamdevLiveStreamer(); virtual ~cStreamdevLiveStreamer();
void SetDevice(cDevice *Device) { m_Device = Device; } void SetDevice(cDevice *Device) { m_Device = Device; }

420
server/menuHTTP.c Normal file
View File

@ -0,0 +1,420 @@
#include <vdr/channels.h>
#include "server/menuHTTP.h"
//**************************** cChannelIterator **************
cChannelIterator::cChannelIterator(cChannel *First): channel(First)
{}
const cChannel* cChannelIterator::Next()
{
const cChannel *current = channel;
channel = NextChannel(channel);
return current;
}
//**************************** cListAll **************
cListAll::cListAll(): cChannelIterator(Channels.First())
{}
const cChannel* cListAll::NextChannel(const cChannel *Channel)
{
if (Channel)
Channel = Channels.Next(Channel);
return Channel;
}
//**************************** cListChannels **************
cListChannels::cListChannels(): cChannelIterator(Channels.Get(Channels.GetNextNormal(-1)))
{}
const cChannel* cListChannels::NextChannel(const cChannel *Channel)
{
if (Channel)
Channel = Channels.Get(Channels.GetNextNormal(Channel->Index()));
return Channel;
}
// ********************* cListGroups ****************
cListGroups::cListGroups(): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
{}
const cChannel* cListGroups::NextChannel(const cChannel *Channel)
{
if (Channel)
Channel = Channels.Get(Channels.GetNextGroup(Channel->Index()));
return Channel;
}
//
// ********************* cListGroup ****************
cListGroup::cListGroup(const cChannel *Group): cChannelIterator((Group && Group->GroupSep() && Channels.Next(Group) && !Channels.Next(Group)->GroupSep()) ? Channels.Next(Group) : NULL)
{}
const cChannel* cListGroup::NextChannel(const cChannel *Channel)
{
if (Channel)
Channel = Channels.Next(Channel);
return (Channel && !Channel->GroupSep()) ? Channel : NULL;
}
//
// ********************* cListTree ****************
cListTree::cListTree(const cChannel *SelectedGroup): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
{
selectedGroup = SelectedGroup;
currentGroup = Channels.Get(Channels.GetNextGroup(-1));
}
const cChannel* cListTree::NextChannel(const cChannel *Channel)
{
if (currentGroup == selectedGroup)
{
if (Channel)
Channel = Channels.Next(Channel);
if (Channel && Channel->GroupSep())
currentGroup = Channel;
}
else
{
if (Channel)
Channel = Channels.Get(Channels.GetNextGroup(Channel->Index()));
currentGroup = Channel;
}
return Channel;
}
// ******************** cChannelList ******************
cChannelList::cChannelList(cChannelIterator *Iterator) : iterator(Iterator)
{}
cChannelList::~cChannelList()
{
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 =
"[<a href=\"/\">Home</a> (<a href=\"all.html\">no script</a>)] "
"[<a href=\"tree.html\">Tree View</a>] "
"[<a href=\"groups.html\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
"[<a href=\"channels.html\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
const char* cHtmlChannelList::css =
"<style type=\"text/css\">\n"
"<!--\n"
"a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n"
"body { font:100% Verdana, Arial, Helvetica, sans-serif; background-color:#9999FF; margin:1em; }\n"
".menu { position:fixed; top:0px; left:1em; right:1em; height:3em; text-align:center; background-color:white; border:inset 2px #9999ff; }\n"
"h2 { font-size:150%; margin:0em; padding:0em 1.5em; }\n"
".contents { margin-top:5em; background-color:white; }\n"
".group { background:url(data:image/gif;base64,R0lGODdhAQAeAIQeAJub/5yc/6Cf/6Oj/6am/6qq/66u/7Gx/7S0/7i4/7u8/7+//8LD/8bG/8nK/83N/9HQ/9TU/9fX/9va/97e/+Lh/+Xl/+no/+3t//Dw//Pz//b3//v7//7+/////////ywAAAAAAQAeAAAFGCAQCANRGAeSKAvTOA8USRNVWReWaRvXhQA7) repeat-x; border:inset 2px #9999ff; }\n"
".items { border-top:dashed 1px; margin-top:0px; margin-bottom:0px; padding:0.7em 5em; }\n"
".apid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJAjI+pywj5WgPxVAmpNRcHqnGb94FPhE7m+okts7JvusSmSys2iLv6TstldjMfhhUkcXi+zjFUVFo6TiVVij0UAAA7) no-repeat; }\n"
".dpid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJFjI+py+0BopwAUoqivRvr83UaZ4RWMnVoBbLZaJbuqcCLGcv0+t5Vvgu2hLrh6pfDzVSpnlGEbAZhnIutZaVmH9yuV1EAADs=) no-repeat; }\n"
"button { width:2em; margin:0.2em 0.5em; vertical-align:top; }\n"
"-->\n"
"</style>";
const char* cHtmlChannelList::js =
"<script language=\"JavaScript\">\n"
"<!--\n"
"function eventTarget(evt) {\n"
" if (!evt) evt = window.event;\n"
" if (evt.target) return evt.target;\n"
" else if (evt.srcElement) return evt.srcElement;\n"
" else return null;\n"
"}\n"
// toggle visibility of a group
"function clickHandler(evt) {\n"
" var button = eventTarget(evt);\n"
" if (button) {\n"
" var group = document.getElementById('c' + button.id);\n"
" if (group) {\n"
" button.removeChild(button.firstChild);\n"
" if (group.style.display == 'block') {\n"
" button.appendChild(document.createTextNode(\"+\"));\n"
" group.style.display = 'none';\n"
" } else {\n"
" button.appendChild(document.createTextNode(\"-\"));\n"
" group.style.display = 'block';\n"
" }\n"
" }\n"
" }\n"
"}\n"
// insert a click button infront of each h2 and an id to the corresponding list
"function init() {\n"
" var titles = document.getElementsByTagName('h2');\n"
" for (var i = 0; i < titles.length; i++) {\n"
" var button = document.createElement('button');\n"
" button.id = 'g' + i;\n"
" button.onclick = clickHandler;\n"
" button.appendChild(document.createTextNode('+'));\n"
" titles[i].insertBefore(button, titles[i].firstChild);\n"
" var group = titles[i].nextSibling;\n"
" while (group) {\n"
" if (group.className && group.className == 'items') {\n"
" group.id = 'cg' + i;\n"
" break;\n"
" }\n"
" group = group.nextSibling;\n"
" }\n"
" }\n"
"}\n"
"window.onload = init;\n"
// hide lists before the browser renders it
"if (document.styleSheets[0].insertRule)\n"
" document.styleSheets[0].insertRule('.items { display:none }', 0);\n"
"else if (document.styleSheets[0].addRule)\n"
" document.styleSheets[0].addRule('.items', 'display:none');\n"
"//-->\n"
"</script>";
std::string cHtmlChannelList::StreamTypeMenu()
{
std::string typeMenu;
typeMenu += (streamType == stTS ? (std::string) "[TS] " :
(std::string) "[<a href=\"/TS/" + self + "\">TS</a>] ");
typeMenu += (streamType == stPS ? (std::string) "[PS] " :
(std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
typeMenu += (streamType == stPES ? (std::string) "[PES] " :
(std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
typeMenu += (streamType == stES ? (std::string) "[ES] " :
(std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
typeMenu += (streamType == stExtern ? (std::string) "[Extern] " :
(std::string) "[<a href=\"/Extern/" + self + "\">Extern</a>] ");
return typeMenu;
}
cHtmlChannelList::cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget): cChannelList(Iterator)
{
streamType = StreamType;
self = strdup(Self);
groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL;
htmlState = hsRoot;
current = NULL;
}
cHtmlChannelList::~cHtmlChannelList()
{
free((void *) self);
free((void *) groupTarget);
}
bool cHtmlChannelList::HasNext()
{
return htmlState != hsPageBottom;
}
std::string cHtmlChannelList::Next()
{
switch (htmlState)
{
case hsRoot:
htmlState = hsHtmlHead;
break;
case hsHtmlHead:
htmlState = hsCss;
break;
case hsCss:
htmlState = *self ? hsPageTop : hsJs;
break;
case hsJs:
htmlState = hsPageTop;
break;
case hsPageTop:
current = NextChannel();
htmlState = current ? (current->GroupSep() ? hsGroupTop : hsPlainTop) : hsPageBottom;
break;
case hsPlainTop:
htmlState = hsPlainItem;
break;
case hsPlainItem:
current = NextChannel();
htmlState = current && !current->GroupSep() ? hsPlainItem : hsPlainBottom;
break;
case hsPlainBottom:
htmlState = current ? hsGroupTop : hsPageBottom;
break;
case hsGroupTop:
current = NextChannel();
htmlState = current && !current->GroupSep() ? hsItemsTop : hsGroupBottom;
break;
case hsItemsTop:
htmlState = hsItem;
break;
case hsItem:
current = NextChannel();
htmlState = current && !current->GroupSep() ? hsItem : hsItemsBottom;
break;
case hsItemsBottom:
htmlState = hsGroupBottom;
break;
case hsGroupBottom:
htmlState = current ? hsGroupTop : hsPageBottom;
break;
case hsPageBottom:
default:
esyslog("streamdev-server cHtmlChannelList: invalid call to Next()");
break;
}
switch (htmlState)
{
// NOTE: JavaScript requirements:
// Group title is identified by <h2> tag
// Channel list must be a sibling of <h2> with class "items"
case hsHtmlHead: return "<html><head>" + HtmlHead();
case hsCss: return css;
case hsJs: return js;
case hsPageTop: return "</head><body>" + PageTop() + "<div class=\"contents\">";
case hsGroupTop: return "<div class=\"group\"><h2>" + GroupTitle() + "</h2>";
case hsItemsTop:
case hsPlainTop: return "<ol class=\"items\">";
case hsItem:
case hsPlainItem: return ItemText();
case hsItemsBottom:
case hsPlainBottom: return "</ol>";
case hsGroupBottom: return "</div>";
case hsPageBottom: return "</div>" + PageBottom() + "</body></html>";
default: return "";
}
}
std::string cHtmlChannelList::HtmlHead()
{
return (std::string) "";
}
std::string cHtmlChannelList::PageTop()
{
return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>";
}
std::string cHtmlChannelList::PageBottom()
{
return (std::string) "";
}
std::string cHtmlChannelList::GroupTitle()
{
if (groupTarget)
{
return (std::string) "<a href=\"" + groupTarget + "?group=" +
(const char*) itoa(cChannelList::GetGroupIndex(current)) +
"\">" + current->Name() + "</a>";
}
else
{
return (std::string) current->Name();
}
}
std::string cHtmlChannelList::ItemText()
{
std::string line;
line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + "\">" +
current->Name() + "</a>";
int count = 0;
for (int i = 0; current->Apid(i) != 0; ++i, ++count)
;
for (int i = 0; current->Dpid(i) != 0; ++i, ++count)
;
if (count > 1)
{
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) + "\" class=\"apid\">" + 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) + "\" class=\"dpid\">" + current->Dlang(i) + "</a>";
}
}
line += "</li>";
return line;
}
// ******************** cM3uChannelList ******************
cM3uChannelList::cM3uChannelList(cChannelIterator *Iterator, const char* Base)
: cChannelList(Iterator)
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
, m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
#endif
{
base = strdup(Base);
m3uState = msFirst;
}
cM3uChannelList::~cM3uChannelList()
{
free(base);
}
bool cM3uChannelList::HasNext()
{
return m3uState != msLast;
}
std::string cM3uChannelList::Next()
{
if (m3uState == msFirst)
{
m3uState = msContinue;
return "#EXTM3U";
}
const cChannel *channel = NextChannel();
if (!channel)
{
m3uState = msLast;
return "";
}
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
std::string name = (std::string) m_IConv.Convert(channel->Name());
#else
std::string name = channel->Name();
#endif
if (channel->GroupSep())
{
return (std::string) "#EXTINF:0," + name + "\r\n" +
base + "group.m3u?group=" +
(const char*) itoa(cChannelList::GetGroupIndex(channel));
}
else
{
return (std::string) "#EXTINF:0," +
(const char*) itoa(channel->Number()) + " " + name + "\r\n" +
base + (std::string) channel->GetChannelID().ToString();
}
}

140
server/menuHTTP.h Normal file
View File

@ -0,0 +1,140 @@
#ifndef VDR_STREAMDEV_SERVERS_MENUHTTP_H
#define VDR_STREAMDEV_SERVERS_MENUHTTP_H
#include <string>
#include "../common.h"
class cChannel;
// ******************** cChannelIterator ******************
class cChannelIterator
{
private:
const cChannel *channel;
protected:
virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
public:
const cChannel* Next();
cChannelIterator(cChannel *First);
virtual ~cChannelIterator() {};
};
class cListAll: public cChannelIterator
{
protected:
virtual const cChannel* NextChannel(const cChannel *Channel);
public:
cListAll();
virtual ~cListAll() {};
};
class cListChannels: public cChannelIterator
{
protected:
virtual const cChannel* NextChannel(const cChannel *Channel);
public:
cListChannels();
virtual ~cListChannels() {};
};
class cListGroups: public cChannelIterator
{
protected:
virtual const cChannel* NextChannel(const cChannel *Channel);
public:
cListGroups();
virtual ~cListGroups() {};
};
class cListGroup: public cChannelIterator
{
protected:
virtual const cChannel* NextChannel(const cChannel *Channel);
public:
cListGroup(const cChannel *Group);
virtual ~cListGroup() {};
};
class cListTree: public cChannelIterator
{
private:
const cChannel* selectedGroup;
const cChannel* currentGroup;
protected:
virtual const cChannel* NextChannel(const cChannel *Channel);
public:
cListTree(const cChannel *SelectedGroup);
virtual ~cListTree() {};
};
// ******************** cChannelList ******************
class cChannelList
{
private:
cChannelIterator *iterator;
protected:
const cChannel* NextChannel() { return iterator->Next(); }
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();
};
class cHtmlChannelList: public cChannelList
{
private:
static const char* menu;
static const char* css;
static const char* js;
enum eHtmlState {
hsRoot, hsHtmlHead, hsCss, hsJs, hsPageTop, hsPageBottom,
hsGroupTop, hsGroupBottom,
hsPlainTop, hsPlainItem, hsPlainBottom,
hsItemsTop, hsItem, hsItemsBottom
};
eHtmlState htmlState;
const cChannel *current;
eStreamType streamType;
const char* self;
const char* groupTarget;
std::string StreamTypeMenu();
std::string HtmlHead();
std::string PageTop();
std::string GroupTitle();
std::string ItemText();
std::string PageBottom();
public:
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: text/html\r\n\r\n"; }
virtual bool HasNext();
virtual std::string Next();
cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget);
virtual ~cHtmlChannelList();
};
class cM3uChannelList: public cChannelList
{
private:
char *base;
enum eM3uState { msFirst, msContinue, msLast };
eM3uState m3uState;
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
cCharSetConv m_IConv;
#endif
public:
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl\r\n"; };
virtual bool HasNext();
virtual std::string Next();
cM3uChannelList(cChannelIterator *Iterator, const char* Base);
virtual ~cM3uChannelList();
};
#endif

View File

@ -16,7 +16,7 @@ cTBSelect::~cTBSelect() {
int cTBSelect::Select(uint TimeoutMs) { int cTBSelect::Select(uint TimeoutMs) {
struct timeval tv; struct timeval tv;
ssize_t res; ssize_t res = 0;
int ms; int ms;
tv.tv_usec = (TimeoutMs % 1000) * 1000; tv.tv_usec = (TimeoutMs % 1000) * 1000;

View File

@ -153,5 +153,5 @@ bool cTBSocket::Shutdown(int how) {
bool cTBSocket::SetDSCP(void) { bool cTBSocket::SetDSCP(void) {
int dscp = STREAMDEV_DSCP; int dscp = STREAMDEV_DSCP;
return ::setsockopt(*this, SOL_IP, IP_TOS, &dscp, sizeof(dscp)) != -1; return ::setsockopt(*this, IPPROTO_IP, IP_TOS, &dscp, sizeof(dscp)) != -1;
} }