mirror of
https://projects.vdr-developer.org/git/vdr-plugin-streamdev.git
synced 2023-10-10 19:16:51 +02:00
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:
parent
79836e69a9
commit
b66bf7a698
@ -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
|
||||
|
5
HISTORY
5
HISTORY
@ -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
|
||||
|
4
Makefile
4
Makefile
@ -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
15
README
@ -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:
|
||||
----------------------------
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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; }
|
||||
|
Loading…
Reference in New Issue
Block a user