From 31df0eaf8e49bc1cfea755bd88f3dd795c8f1ace Mon Sep 17 00:00:00 2001 From: Frank Schmirler Date: Thu, 2 Dec 2010 09:02:31 +0100 Subject: [PATCH] Streamdev 0.3.4 --- CONTRIBUTORS | 59 +++++- HISTORY | 88 +++++++++ Makefile | 18 +- README | 170 ++++++++-------- client/device.c | 10 +- client/socket.c | 12 +- common.c | 4 +- common.h | 8 +- libdvbmpeg/ctools.c | 24 +++ remux/extern.c | 13 +- remux/extern.h | 3 +- server/connectionHTTP.c | 200 ++++++++++++------- server/connectionHTTP.h | 7 +- server/connectionVTP.c | 26 ++- server/connectionVTP.h | 1 + server/livestreamer.c | 48 ++++- server/livestreamer.h | 3 +- server/menuHTTP.c | 420 ++++++++++++++++++++++++++++++++++++++++ server/menuHTTP.h | 140 ++++++++++++++ tools/select.c | 2 +- tools/socket.c | 2 +- 21 files changed, 1064 insertions(+), 194 deletions(-) create mode 100644 server/menuHTTP.c create mode 100644 server/menuHTTP.h diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f872f9a..6f9c812 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,21 +1,72 @@ 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 - for reporting problems with the Elchi-Patch + for reporting problems with the Elchi-Patch Michal for sending a patch to select the HTTP streamtype via remote 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 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 diff --git a/HISTORY b/HISTORY index 5018176..160994e 100644 --- a/HISTORY +++ b/HISTORY @@ -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 +- 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 diff --git a/Makefile b/Makefile index 8529750..e4a17c4 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README b/README index b69c5c5..5a82acb 100644 --- a/README +++ b/README @@ -1,10 +1,12 @@ This is a "plugin" for the Video Disk Recorder (VDR). Written by: Sascha Volkenandt +Current maintainer: Frank Schmirler -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 @@ -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; } diff --git a/common.c b/common.c index f422c5e..4759515 100644 --- a/common.c +++ b/common.c @@ -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 @@ -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", diff --git a/common.h b/common.h index 1d14883..2565604 100644 --- a/common.h +++ b/common.h @@ -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 #include diff --git a/libdvbmpeg/ctools.c b/libdvbmpeg/ctools.c index 76ba48d..4766ea2 100644 --- a/libdvbmpeg/ctools.c +++ b/libdvbmpeg/ctools.c @@ -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); diff --git a/remux/extern.c b/remux/extern.c index 01d4b33..e137c4a 100644 --- a/remux/extern.c +++ b/remux/extern.c @@ -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); } diff --git a/remux/extern.h b/remux/extern.h index ae055ac..7a44852 100644 --- a/remux/extern.h +++ b/remux/extern.h @@ -3,6 +3,7 @@ #include "remux/tsremux.h" #include +#include 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); diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index 8bde3aa..38a82a3 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -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 #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("VDR Channel Listing") - && Respond("
    "); + 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("
"); - DeferClose(); - m_Status = hsFinished; + 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)"
  • --- " + m_ListChannel->Name() + "---
  • "; - else { - int index = 1; - line = (std::string)"
  • GetChannelID().ToString() + "\">" - + m_ListChannel->Name() + " "; - for (int i = 0; m_ListChannel->Apid(i) != 0; ++i, ++index) { - line += "GetChannelID().ToString() + "+" - + (const char*)itoa(index) + "\">(" - + m_ListChannel->Alang(i) + ") "; - } - for (int i = 0; m_ListChannel->Dpid(i) != 0; ++i, ++index) { - line += "GetChannelID().ToString() + "+" - + (const char*)itoa(index) + "\">(" - + m_ListChannel->Dlang(i) + ") "; - } - line += "
  • "; - } - 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(); - m_Job = hjListing; + 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; } diff --git a/server/connectionHTTP.h b/server/connectionHTTP.h index 11e97b7..a3558ad 100644 --- a/server/connectionHTTP.h +++ b/server/connectionHTTP.h @@ -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 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); diff --git a/server/connectionVTP.c b/server/connectionVTP.c index 5ca2df6..2829c13 100644 --- a/server/connectionVTP.c +++ b/server/connectionVTP.c @@ -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_FilterStreamer) - m_FilterStreamer = new cStreamdevFilterStreamer; - m_FilterStreamer->SetDevice(dev); - //m_FilterStreamer->SetChannel(chan); + if(m_FiltersSupport) { + if(!m_FilterStreamer) + m_FilterStreamer = new cStreamdevFilterStreamer; + m_FilterStreamer->SetDevice(dev); + //m_FilterStreamer->SetChannel(chan); + } #endif return Respond(220, "Channel tuned"); diff --git a/server/connectionVTP.h b/server/connectionVTP.h index aa9a90f..3acb1a2 100644 --- a/server/connectionVTP.h +++ b/server/connectionVTP.h @@ -24,6 +24,7 @@ private: char *m_LastCommand; eStreamType m_StreamType; + bool m_FiltersSupport; // Members adopted for SVDRP cRecordings Recordings; diff --git a/server/livestreamer.c b/server/livestreamer.c index 1bbeddb..5e19d2b 100644 --- a/server/livestreamer.c +++ b/server/livestreamer.c @@ -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: diff --git a/server/livestreamer.h b/server/livestreamer.h index 1973f71..57c3a90 100644 --- a/server/livestreamer.h +++ b/server/livestreamer.h @@ -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; } diff --git a/server/menuHTTP.c b/server/menuHTTP.c new file mode 100644 index 0000000..b5bb299 --- /dev/null +++ b/server/menuHTTP.c @@ -0,0 +1,420 @@ +#include +#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 = + "[Home (no script)] " + "[Tree View] " + "[Groups (Playlist)] " + "[Channels (Playlist)] "; + +const char* cHtmlChannelList::css = + ""; + +const char* cHtmlChannelList::js = + ""; + + +std::string cHtmlChannelList::StreamTypeMenu() +{ + std::string typeMenu; + typeMenu += (streamType == stTS ? (std::string) "[TS] " : + (std::string) "[TS] "); + typeMenu += (streamType == stPS ? (std::string) "[PS] " : + (std::string) "[PS] "); + typeMenu += (streamType == stPES ? (std::string) "[PES] " : + (std::string) "[PES] "); + typeMenu += (streamType == stES ? (std::string) "[ES] " : + (std::string) "[ES] "); + typeMenu += (streamType == stExtern ? (std::string) "[Extern] " : + (std::string) "[Extern] "); + 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

    tag + // Channel list must be a sibling of

    with class "items" + case hsHtmlHead: return "" + HtmlHead(); + case hsCss: return css; + case hsJs: return js; + case hsPageTop: return "" + PageTop() + "
    "; + case hsGroupTop: return "

    " + GroupTitle() + "

    "; + case hsItemsTop: + case hsPlainTop: return "
      "; + case hsItem: + case hsPlainItem: return ItemText(); + case hsItemsBottom: + case hsPlainBottom: return "
    "; + case hsGroupBottom: return "
    "; + case hsPageBottom: return "
    " + PageBottom() + ""; + default: return ""; + } +} + +std::string cHtmlChannelList::HtmlHead() +{ + return (std::string) ""; +} + +std::string cHtmlChannelList::PageTop() +{ + return (std::string) "
    " + menu + "
    " + StreamTypeMenu() + "
    "; +} + +std::string cHtmlChannelList::PageBottom() +{ + return (std::string) ""; +} + +std::string cHtmlChannelList::GroupTitle() +{ + if (groupTarget) + { + return (std::string) "" + current->Name() + ""; + } + else + { + return (std::string) current->Name(); + } +} + +std::string cHtmlChannelList::ItemText() +{ + std::string line; + line += (std::string) "
  • Number()) + "\">"; + line += (std::string) "GetChannelID().ToString() + "\">" + + current->Name() + ""; + + 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) " GetChannelID().ToString() + + "+" + (const char*)itoa(index) + "\" class=\"apid\">" + current->Alang(i) + ""; + } + for (int i = 0; current->Dpid(i) != 0; ++i, ++index) { + line += (std::string) " GetChannelID().ToString() + + "+" + (const char*)itoa(index) + "\" class=\"dpid\">" + current->Dlang(i) + ""; + } + } + line += "
  • "; + 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(); + } +} + diff --git a/server/menuHTTP.h b/server/menuHTTP.h new file mode 100644 index 0000000..8be613b --- /dev/null +++ b/server/menuHTTP.h @@ -0,0 +1,140 @@ +#ifndef VDR_STREAMDEV_SERVERS_MENUHTTP_H +#define VDR_STREAMDEV_SERVERS_MENUHTTP_H + +#include +#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 diff --git a/tools/select.c b/tools/select.c index 9568110..2a3abe5 100644 --- a/tools/select.c +++ b/tools/select.c @@ -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; diff --git a/tools/socket.c b/tools/socket.c index e9266c5..7f4ce0a 100644 --- a/tools/socket.c +++ b/tools/socket.c @@ -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; }