diff options
-rw-r--r-- | CONTRIBUTORS | 2 | ||||
-rw-r--r-- | HISTORY | 5 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | README | 15 | ||||
-rw-r--r-- | remux/extern.c | 13 | ||||
-rw-r--r-- | remux/extern.h | 3 | ||||
-rw-r--r-- | server/connectionHTTP.c | 198 | ||||
-rw-r--r-- | server/connectionHTTP.h | 7 | ||||
-rw-r--r-- | server/livestreamer.c | 5 | ||||
-rw-r--r-- | server/livestreamer.h | 3 |
10 files changed, 172 insertions, 83 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2894921..6f9c812 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -23,6 +23,7 @@ Rolf Ahrenberg 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 @@ -52,6 +53,7 @@ Petri Hintukainen 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 @@ -1,6 +1,11 @@ VDR Plugin 'streamdev' Revision History --------------------------------------- +- 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 @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.10 2008/03/12 09:36:27 schmirl Exp $ +# $Id: Makefile,v 1.11 2008/03/28 15:11:40 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 @@ -154,7 +154,15 @@ 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 @@ -173,6 +181,11 @@ 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: ---------------------------- 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 <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); 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 <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>"); - DeferClose(); - m_Status = hsFinished; - 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> "; + if (m_ChannelList) { + if (m_ChannelList->HasNext()) { + if (!Respond("%s", true, m_ChannelList->Next().c_str())) + DeferClose(); } - 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> "; + else { + DELETENULL(m_ChannelList); + m_Status = hsFinished; + DeferClose(); } - line += "</li>"; + return; } - 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(); + + // 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()); - if (strncasecmp(ptr, "PS/", 3) == 0) { + 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<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); diff --git a/server/livestreamer.c b/server/livestreamer.c index cd3c696..5e19d2b 100644 --- a/server/livestreamer.c +++ b/server/livestreamer.c @@ -323,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), @@ -488,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; } |