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
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
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)
for providing italian language texts
@ -13,9 +16,57 @@ Michal
Rolf Ahrenberg
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
for providing vdr-incompletesections.diff
Thomas Keil
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
---------------------------------------
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
- dropped support for non-ts streaming in vdr-to-vdr clients

View File

@ -1,7 +1,7 @@
#
# 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.
# 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/componentHTTP.o server/componentVTP.o server/connection.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
@ -85,8 +85,10 @@ ifeq ($(shell test -f $(VDRDIR)/sections.c ; echo $$?),0)
DEFINES += -DHAVE_AUTOPID
endif
libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.cc libdvbmpeg/*.h libdvbmpeg/*.hh
make -C ./libdvbmpeg libdvbmpegtools.a
### The main target:
.PHONY: all dist clean
all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so
### Implicit rules:
@ -113,7 +115,9 @@ endif
### 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)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
@ -126,10 +130,10 @@ dist: clean
@-rm -rf $(TMPDIR)/$(ARCHIVE)
@mkdir $(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)
@echo Distribution package created as $(PACKAGE).tgz
clean:
@-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).
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.
@ -14,16 +16,12 @@ Contents:
1. Description
2. Installation
2.1 VDR 1.2.X
2.2 VDR 1.3.X
2.2 VDR 1.3.X and above
3. Usage
3.1 Usage VDR-to-VDR server
3.2 Usage HTTP server
3.1 Usage HTTP server
3.2 Usage VDR-to-VDR server
3.3 Usage VDR-to-VDR client
3.4 General Usage Notes
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]
4. Other useful Plugins
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] plugins
2.2 VDR 1.3.X:
--------------
2.2 VDR 1.3.X and above:
------------------------
cd vdr-1.X.X/PLUGINS/src
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
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
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
cards are in use (i.e. when something is recorded, or by multiple clients),
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
@ -164,8 +152,17 @@ TS Transport Stream (i.e. a dump from the device)
PES Packetized Elemetary Stream (VDR's native recording format)
PS Program Stream (SVCD, DVD like stream)
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/S19.2E-0-12480-898
@ -178,12 +175,26 @@ http://hostname:3000/TS/3
http://hostname:3000/PES/S19.2E-0-12480-898
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
format explicitly if you want to listen to radio channels. Play them pack i.e.
with mpg123.
Possible values are 'PES', 'TS', 'PS', 'ES' and 'EXTERN'. You need to specify
the ES format explicitly if you want to listen to radio channels. Play them
back i.e. with mpg123.
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:
----------------------------
@ -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
transponder.
You can choose a remote streamtype in the setup. I'd suggest TS streaming as
it has a much shorter delay than PES streaming (compared to live-view of the
same channel on the server), and transmits more information such as AC3 and
teletext data.
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).
When setting the parameter "MultiPID streaming" to yes (the default) (only
applies if the streamtype is TS), 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).
With "Filter Streaming" enabled, the client will receive meta information like
EPG data and service information, just as if the client had its own DVB card.
Link channels and even a client-side EPG scan have been reported to work.
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
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
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
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:
4. Other useful Plugins:
------------------------
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.
I never tried, and probably I never will, so don't ask about it please ;)
* femon (http://www.saunalahti.fi/~rahrenbe/vdr/femon/)
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
mode.
4.2 Plugins for Server:
-----------------------
* 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
------------
Works with ivtv and analogue cards according to Andreas Kool.
4.3 Alternatives:
-----------------
* 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:
------------------
- Recordings & Timers on the client side could endanger Timers & Recordings on
the server, as they will have the same priority (by default). Set the
default priority to i.e. 40 if you want the server to supersede the client.
- Sometimes, if you reload VDR too often (for example while recompiling), the
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 :-(.
* In VDR-to-VDR setup, the availability of a channel is checked with a different
priority than the actual channel switch. The later always uses priority 0.
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
client side recordings or set the priority of client side timers to 0.

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"
@ -212,8 +212,16 @@ void cStreamdevDevice::CloseDvrInt(void) {
}
Dprintf("cStreamdevDevice::CloseDvrInt(): Closing DVR connection\n");
#if VDRVERSNUM < 10500
DELETENULL(m_TSBuffer);
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) {

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>
@ -140,8 +140,14 @@ bool cClientSocket::CheckConnection(void) {
return false;
}
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS",
RemoteIp().c_str(), RemotePort());
const char *Filters = "";
#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;
}

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>
@ -11,7 +11,7 @@
using namespace std;
const char *VERSION = "0.3.3-20070921";
const char *VERSION = "0.3.4";
const char *StreamTypes[st_Count] = {
"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
#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/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);
#ifdef __FreeBSD__
if ( (fdin = open(name, O_RDONLY)) < 0){
#else
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
#endif
fprintf(stderr,"Can't open %s\n",name);
exit(1);
}
@ -2101,8 +2105,12 @@ void split_mpg(char *name, uint64_t size)
sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
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
|O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){
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);
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
|O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){
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);
#ifdef __FreeBSD__
if ( (fdin = open(name, O_RDONLY)) < 0){
#else
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
#endif
fprintf(stderr,"Can't open %s\n",name);
exit(1);
}
@ -2182,8 +2198,12 @@ void cut_mpg(char *name, uint64_t size)
sprintf(new_name,"%s-1.%s",base_name,ext);
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
|O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){
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);
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
|O_LARGEFILE,
#endif
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
S_IROTH|S_IWOTH)) < 0){
fprintf(stderr,"Can't open %s\n",new_name);

View File

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

View File

@ -3,6 +3,7 @@
#include "remux/tsremux.h"
#include <vdr/ringbuffer.h>
#include <string>
extern const char *g_ExternRemux;
@ -14,7 +15,7 @@ private:
cTSExt *m_Remux;
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();
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 "server/connectionHTTP.h"
#include "server/menuHTTP.h"
#include "server/setup.h"
cConnectionHTTP::cConnectionHTTP(void):
cServerConnection("HTTP"),
m_Status(hsRequest),
m_LiveStreamer(NULL),
m_StreamerParameter(""),
m_Channel(NULL),
m_Apid(0),
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
m_ListChannel(NULL)
m_ChannelList(NULL)
{
Dprintf("constructor hsRequest\n");
}
@ -39,6 +41,10 @@ bool cConnectionHTTP::Command(char *Cmd)
m_Status = hsBody;
return ProcessRequest();
}
if (strncasecmp(Cmd, "Host:", 5) == 0) {
Dprintf("Host-Header\n");
m_Host = (std::string) skipspace(Cmd + 5);
}
Dprintf("header\n");
return true;
default:
@ -53,11 +59,9 @@ bool cConnectionHTTP::ProcessRequest(void)
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
switch (m_Job) {
case hjListing:
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: text/html")
&& Respond("")
&& Respond("<html><head><title>VDR Channel Listing</title></head>")
&& Respond("<body><ul>");
if (m_ChannelList)
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
break;
case hjTransfer:
if (m_Channel == NULL) {
@ -65,7 +69,7 @@ bool cConnectionHTTP::ProcessRequest(void)
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);
if (device != NULL) {
device->SwitchChannel(m_Channel, false);
@ -106,43 +110,21 @@ void cConnectionHTTP::Flushed(void)
switch (m_Job) {
case hjListing:
if (m_ListChannel == NULL) {
Respond("</ul></body></html>");
if (m_ChannelList) {
if (m_ChannelList->HasNext()) {
if (!Respond("%s", true, m_ChannelList->Next().c_str()))
DeferClose();
}
else {
DELETENULL(m_ChannelList);
m_Status = hsFinished;
DeferClose();
}
return;
}
if (m_ListChannel->GroupSep())
line = (std::string)"<li>--- " + m_ListChannel->Name() + "---</li>";
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);
// should never be reached
esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list");
m_Status = hsFinished;
break;
case hjTransfer:
@ -155,49 +137,131 @@ void cConnectionHTTP::Flushed(void)
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;
int apid = 0;
ptr = skipspace(ptr);
while (*ptr == '/')
++ptr;
ptr = Opts.c_str();
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;
ptr += 3;
} else if (strncasecmp(ptr, "PES/", 4) == 0) {
} else if (strcasecmp(pType, "PES") == 0) {
m_StreamType = stPES;
ptr += 4;
} else if (strncasecmp(ptr, "TS/", 3) == 0) {
} else if (strcasecmp(pType, "TS") == 0) {
m_StreamType = stTS;
ptr += 3;
} else if (strncasecmp(ptr, "ES/", 3) == 0) {
} else if (strcasecmp(pType, "ES") == 0) {
m_StreamType = stES;
ptr += 3;
} else if (strncasecmp(ptr, "Extern/", 3) == 0) {
} else if (strcasecmp(pType, "Extern") == 0) {
m_StreamType = stExtern;
ptr += 7;
}
while (*ptr == '/')
++ptr;
for (ep = ptr + strlen(ptr); ep >= ptr && !isspace(*ep); --ep)
;
std::string groupTarget;
cChannelIterator *iterator = NULL;
std::string filespec = Opts.substr(ptr - sp, ep - ptr);
Dprintf("substr: %s\n", filespec.c_str());
if (filespec.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);
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 (filespec == "" || filespec.substr(0, 12) == "channels.htm") {
m_ListChannel = Channels.First();
if (iterator) {
if (filespec.empty() || fileext.compare(".htm") == 0 || fileext.compare(".html") == 0) {
m_ChannelList = new cHtmlChannelList(iterator, m_StreamType, (filespec + fileext + query).c_str(), groupTarget.c_str());
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) {
m_Channel = chan;
m_Apid = apid;
Dprintf("Apid is %d\n", apid);
m_Job = hjTransfer;
}
} else
return false;
Dprintf("after channelfromstring\n");
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
@ -12,6 +12,7 @@
class cChannel;
class cStreamdevLiveStreamer;
class cChannelList;
class cConnectionHTTP: public cServerConnection {
private:
@ -28,16 +29,18 @@ private:
};
std::string m_Request;
std::string m_Host;
//std::map<std::string,std::string> m_Headers; TODO: later?
eHTTPStatus m_Status;
eHTTPJob m_Job;
// job: transfer
cStreamdevLiveStreamer *m_LiveStreamer;
std::string m_StreamerParameter;
const cChannel *m_Channel;
int m_Apid;
eStreamType m_StreamType;
// job: listing
const cChannel *m_ListChannel;
cChannelList *m_ChannelList;
protected:
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"
@ -186,7 +186,11 @@ bool cLSTEHandler::Next(bool &Last)
case Event:
if (m_Event != NULL) {
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(),
#endif
m_Event->StartTime(), m_Event->Duration(),
m_Event->TableID());
} else {
@ -225,7 +229,11 @@ bool cLSTEHandler::Next(bool &Last)
case Vps:
m_State = EndEvent;
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());
#endif
else
return Next(Last);
break;
@ -470,6 +478,7 @@ cConnectionVTP::cConnectionVTP(void):
m_FilterStreamer(NULL),
m_LastCommand(NULL),
m_StreamType(stTSPIDS),
m_FiltersSupport(false),
m_LSTEHandler(NULL),
m_LSTCHandler(NULL),
m_LSTTHandler(NULL)
@ -600,8 +609,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
//
// 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);
}
#endif
return Respond(561, "Capability \"%s\" not known", Opts);
@ -672,6 +683,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
#if VDRVERSNUM >= 10300
if (id == siLiveFilter) {
m_FiltersSupport = true;
if(m_FilterStreamer)
m_FilterStreamer->Stop();
delete m_FilterSocket;
@ -735,10 +747,12 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
m_LiveStreamer->Start(m_LiveSocket);
#if VDRVERSNUM >= 10300
if(m_FiltersSupport) {
if(!m_FilterStreamer)
m_FilterStreamer = new cStreamdevFilterStreamer;
m_FilterStreamer->SetDevice(dev);
//m_FilterStreamer->SetChannel(chan);
}
#endif
return Respond(220, "Channel tuned");

View File

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

View File

@ -12,6 +12,8 @@
#include "remux/extern.h"
#include "common.h"
#define TSPATREPACKER
// --- cStreamdevLiveReceiver -------------------------------------------------
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();
if (Length < TS_SIZE-5) {
// 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 */};
memcpy(pat_ts + 5, Data, Length);
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
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
m_Streamer->SetPids(pmtPid);
@ -268,9 +309,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
#if 0
pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT
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
#endif
pids[npids++] = 0x12; // pid 0x12, tid 0x4E...0x6F: EIT
for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); )
if (0 != (pids[npids] = GetPid(stream)) && npids < MAXRECEIVEPIDS)
npids++;
@ -282,9 +323,10 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
// --- cStreamdevLiveStreamer -------------------------------------------------
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority):
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter):
cStreamdevStreamer("streamdev-livestreaming"),
m_Priority(Priority),
m_Parameter(Parameter),
m_NumPids(0),
m_StreamType(stTSPIDS),
m_Channel(NULL),
@ -447,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
case stExtern:
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());
case stTSPIDS:

View File

@ -19,6 +19,7 @@ class cStreamdevLiveReceiver;
class cStreamdevLiveStreamer: public cStreamdevStreamer {
private:
int m_Priority;
std::string m_Parameter;
int m_Pids[MAXRECEIVEPIDS + 1];
int m_NumPids;
eStreamType m_StreamType;
@ -35,7 +36,7 @@ private:
bool HasPid(int Pid);
public:
cStreamdevLiveStreamer(int Priority);
cStreamdevLiveStreamer(int Priority, std::string Parameter = "");
virtual ~cStreamdevLiveStreamer();
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) {
struct timeval tv;
ssize_t res;
ssize_t res = 0;
int ms;
tv.tv_usec = (TimeoutMs % 1000) * 1000;

View File

@ -153,5 +153,5 @@ bool cTBSocket::Shutdown(int how) {
bool cTBSocket::SetDSCP(void) {
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;
}