Rewrite of http menu (#439)

Including
- m3u playlists by Petri Hintukainen (#254)
- way to pass parameters to externremux by Rolf Ahrenberg
- using host header for absolute URLs for better DNAT / Reverse Proxy support
This commit is contained in:
schmirl 2008-03-28 15:11:40 +00:00
parent 79836e69a9
commit b66bf7a698
10 changed files with 173 additions and 84 deletions

View File

@ -23,6 +23,7 @@ Rolf Ahrenberg
for adding a return code check to vasprintf() for adding a return code check to vasprintf()
for suggesting a fix of the Makefile's default target 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 a TS PAT repacker based on Petri Laine's VDR TS recording patch
for making it possible to pass parameters to externremux.sh
Rantanen Teemu Rantanen Teemu
for providing vdr-incompletesections.diff for providing vdr-incompletesections.diff
@ -52,6 +53,7 @@ Petri Hintukainen
for adding PAT, PMT and PCR to HTTP TS streams for adding PAT, PMT and PCR to HTTP TS streams
for fixing a segfault / deadlock when shutting down for fixing a segfault / deadlock when shutting down
for fixing compiler warnings for fixing compiler warnings
for adding M3U playlists
ollo ollo
for suggesting support for WMM capable WLAN accesspoints for suggesting support for WMM capable WLAN accesspoints

View File

@ -1,6 +1,11 @@
VDR Plugin 'streamdev' Revision History 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 - enable section filtering only with compatible clients (thanks to Petri
Hintukainen) Hintukainen)
- fixed compiler warning - fixed compiler warning

View File

@ -1,7 +1,7 @@
# #
# Makefile for a Video Disk Recorder plugin # 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. # The official name of this plugin.
# This name will be used in the '-P...' option of VDR to load the plugin. # This name will be used in the '-P...' option of VDR to load the plugin.
@ -61,7 +61,7 @@ SERVEROBJS = $(PLUGIN)-server.o \
server/server.o server/connectionVTP.o server/connectionHTTP.o \ server/server.o server/connectionVTP.o server/connectionHTTP.o \
server/componentHTTP.o server/componentVTP.o server/connection.o \ server/componentHTTP.o server/componentVTP.o server/connection.o \
server/component.o server/suspend.o server/setup.o server/streamer.o \ server/component.o server/suspend.o server/setup.o server/streamer.o \
server/livestreamer.o server/livefilter.o \ server/livestreamer.o server/livefilter.o server/menuHTTP.o \
\ \
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o

15
README
View File

@ -154,7 +154,15 @@ PS Program Stream (SVCD, DVD like stream)
ES Elementary Stream (only Video, if available, otherwise only Audio) ES Elementary Stream (only Video, if available, otherwise only Audio)
EXTERN Pass stream through external script (e.g. for converting with mencoder) EXTERN Pass stream through external script (e.g. for converting with mencoder)
If you leave the default port (3000), you can access the streams like this: Assuming that you leave the default port (3000), point your web browser to
http://hostname:3000/
You will be presented a menu with links to various channel lists, including M3U
playlist formats.
If you don't want to use the HTML menu or the M3U playlists, you can access the
streams directly like this:
http://hostname:3000/3 http://hostname:3000/3
http://hostname:3000/S19.2E-0-12480-898 http://hostname:3000/S19.2E-0-12480-898
@ -173,6 +181,11 @@ back i.e. with mpg123.
mpg123 http://hostname:3000/ES/200 mpg123 http://hostname:3000/ES/200
With 'EXTERN' you can also add a parameter which is passed as argument to the
externremux script.
http://hostname:3000/EXTERN;some_parameter/3
3.2 Usage VDR-to-VDR server: 3.2 Usage VDR-to-VDR server:
---------------------------- ----------------------------

View File

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

View File

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

View File

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

View File

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

View File

@ -323,9 +323,10 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
// --- cStreamdevLiveStreamer ------------------------------------------------- // --- cStreamdevLiveStreamer -------------------------------------------------
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority): cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter):
cStreamdevStreamer("streamdev-livestreaming"), cStreamdevStreamer("streamdev-livestreaming"),
m_Priority(Priority), m_Priority(Priority),
m_Parameter(Parameter),
m_NumPids(0), m_NumPids(0),
m_StreamType(stTSPIDS), m_StreamType(stTSPIDS),
m_Channel(NULL), m_Channel(NULL),
@ -488,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
case stExtern: case stExtern:
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids()); m_Channel->Spids(), m_Parameter);
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
case stTSPIDS: case stTSPIDS:

View File

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