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 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

View File

@ -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

View File

@ -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

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)
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:
----------------------------

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>");
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)"<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();
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;
}

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

@ -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:

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; }