summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorFrank Schmirler <vdr@schmirler.de>2010-12-02 09:02:31 +0100
committerFrank Schmirler <vdr@schmirler.de>2010-12-02 09:04:50 +0100
commit31df0eaf8e49bc1cfea755bd88f3dd795c8f1ace (patch)
treeedc73b2c6e6af1c2b5d19521fae3a5347dad659d /server
parent7576173547027dae57206cfd3d967d5c516fa6b7 (diff)
downloadvdr-plugin-streamdev-31df0eaf8e49bc1cfea755bd88f3dd795c8f1ace.tar.gz
vdr-plugin-streamdev-31df0eaf8e49bc1cfea755bd88f3dd795c8f1ace.tar.bz2
Streamdev 0.3.4
Diffstat (limited to 'server')
-rw-r--r--server/connectionHTTP.c198
-rw-r--r--server/connectionHTTP.h7
-rw-r--r--server/connectionVTP.c26
-rw-r--r--server/connectionVTP.h1
-rw-r--r--server/livestreamer.c48
-rw-r--r--server/livestreamer.h3
-rw-r--r--server/menuHTTP.c420
-rw-r--r--server/menuHTTP.h140
8 files changed, 764 insertions, 79 deletions
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/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 <vdr/channels.h>
+#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 =
+ "[<a href=\"/\">Home</a> (<a href=\"all.html\">no script</a>)] "
+ "[<a href=\"tree.html\">Tree View</a>] "
+ "[<a href=\"groups.html\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
+ "[<a href=\"channels.html\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
+
+const char* cHtmlChannelList::css =
+ "<style type=\"text/css\">\n"
+ "<!--\n"
+ "a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n"
+ "body { font:100% Verdana, Arial, Helvetica, sans-serif; background-color:#9999FF; margin:1em; }\n"
+ ".menu { position:fixed; top:0px; left:1em; right:1em; height:3em; text-align:center; background-color:white; border:inset 2px #9999ff; }\n"
+ "h2 { font-size:150%; margin:0em; padding:0em 1.5em; }\n"
+ ".contents { margin-top:5em; background-color:white; }\n"
+ ".group { background:url(data:image/gif;base64,R0lGODdhAQAeAIQeAJub/5yc/6Cf/6Oj/6am/6qq/66u/7Gx/7S0/7i4/7u8/7+//8LD/8bG/8nK/83N/9HQ/9TU/9fX/9va/97e/+Lh/+Xl/+no/+3t//Dw//Pz//b3//v7//7+/////////ywAAAAAAQAeAAAFGCAQCANRGAeSKAvTOA8USRNVWReWaRvXhQA7) repeat-x; border:inset 2px #9999ff; }\n"
+ ".items { border-top:dashed 1px; margin-top:0px; margin-bottom:0px; padding:0.7em 5em; }\n"
+ ".apid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJAjI+pywj5WgPxVAmpNRcHqnGb94FPhE7m+okts7JvusSmSys2iLv6TstldjMfhhUkcXi+zjFUVFo6TiVVij0UAAA7) no-repeat; }\n"
+ ".dpid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJFjI+py+0BopwAUoqivRvr83UaZ4RWMnVoBbLZaJbuqcCLGcv0+t5Vvgu2hLrh6pfDzVSpnlGEbAZhnIutZaVmH9yuV1EAADs=) no-repeat; }\n"
+ "button { width:2em; margin:0.2em 0.5em; vertical-align:top; }\n"
+ "-->\n"
+ "</style>";
+
+const char* cHtmlChannelList::js =
+ "<script language=\"JavaScript\">\n"
+ "<!--\n"
+
+ "function eventTarget(evt) {\n"
+ " if (!evt) evt = window.event;\n"
+ " if (evt.target) return evt.target;\n"
+ " else if (evt.srcElement) return evt.srcElement;\n"
+ " else return null;\n"
+ "}\n"
+
+ // toggle visibility of a group
+ "function clickHandler(evt) {\n"
+ " var button = eventTarget(evt);\n"
+ " if (button) {\n"
+ " var group = document.getElementById('c' + button.id);\n"
+ " if (group) {\n"
+ " button.removeChild(button.firstChild);\n"
+ " if (group.style.display == 'block') {\n"
+ " button.appendChild(document.createTextNode(\"+\"));\n"
+ " group.style.display = 'none';\n"
+ " } else {\n"
+ " button.appendChild(document.createTextNode(\"-\"));\n"
+ " group.style.display = 'block';\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}\n"
+
+ // insert a click button infront of each h2 and an id to the corresponding list
+ "function init() {\n"
+ " var titles = document.getElementsByTagName('h2');\n"
+ " for (var i = 0; i < titles.length; i++) {\n"
+ " var button = document.createElement('button');\n"
+ " button.id = 'g' + i;\n"
+ " button.onclick = clickHandler;\n"
+ " button.appendChild(document.createTextNode('+'));\n"
+ " titles[i].insertBefore(button, titles[i].firstChild);\n"
+ " var group = titles[i].nextSibling;\n"
+ " while (group) {\n"
+ " if (group.className && group.className == 'items') {\n"
+ " group.id = 'cg' + i;\n"
+ " break;\n"
+ " }\n"
+ " group = group.nextSibling;\n"
+ " }\n"
+ " }\n"
+ "}\n"
+
+ "window.onload = init;\n"
+
+ // hide lists before the browser renders it
+ "if (document.styleSheets[0].insertRule)\n"
+ " document.styleSheets[0].insertRule('.items { display:none }', 0);\n"
+ "else if (document.styleSheets[0].addRule)\n"
+ " document.styleSheets[0].addRule('.items', 'display:none');\n"
+
+ "//-->\n"
+ "</script>";
+
+
+std::string cHtmlChannelList::StreamTypeMenu()
+{
+ std::string typeMenu;
+ typeMenu += (streamType == stTS ? (std::string) "[TS] " :
+ (std::string) "[<a href=\"/TS/" + self + "\">TS</a>] ");
+ typeMenu += (streamType == stPS ? (std::string) "[PS] " :
+ (std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
+ typeMenu += (streamType == stPES ? (std::string) "[PES] " :
+ (std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
+ typeMenu += (streamType == stES ? (std::string) "[ES] " :
+ (std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
+ typeMenu += (streamType == stExtern ? (std::string) "[Extern] " :
+ (std::string) "[<a href=\"/Extern/" + self + "\">Extern</a>] ");
+ 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 <h2> tag
+ // Channel list must be a sibling of <h2> with class "items"
+ case hsHtmlHead: return "<html><head>" + HtmlHead();
+ case hsCss: return css;
+ case hsJs: return js;
+ case hsPageTop: return "</head><body>" + PageTop() + "<div class=\"contents\">";
+ case hsGroupTop: return "<div class=\"group\"><h2>" + GroupTitle() + "</h2>";
+ case hsItemsTop:
+ case hsPlainTop: return "<ol class=\"items\">";
+ case hsItem:
+ case hsPlainItem: return ItemText();
+ case hsItemsBottom:
+ case hsPlainBottom: return "</ol>";
+ case hsGroupBottom: return "</div>";
+ case hsPageBottom: return "</div>" + PageBottom() + "</body></html>";
+ default: return "";
+ }
+}
+
+std::string cHtmlChannelList::HtmlHead()
+{
+ return (std::string) "";
+}
+
+std::string cHtmlChannelList::PageTop()
+{
+ return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>";
+}
+
+std::string cHtmlChannelList::PageBottom()
+{
+ return (std::string) "";
+}
+
+std::string cHtmlChannelList::GroupTitle()
+{
+ if (groupTarget)
+ {
+ return (std::string) "<a href=\"" + groupTarget + "?group=" +
+ (const char*) itoa(cChannelList::GetGroupIndex(current)) +
+ "\">" + current->Name() + "</a>";
+ }
+ else
+ {
+ return (std::string) current->Name();
+ }
+}
+
+std::string cHtmlChannelList::ItemText()
+{
+ std::string line;
+ line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
+ line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + "\">" +
+ current->Name() + "</a>";
+
+ 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) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
+ "+" + (const char*)itoa(index) + "\" class=\"apid\">" + current->Alang(i) + "</a>";
+ }
+ for (int i = 0; current->Dpid(i) != 0; ++i, ++index) {
+ line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
+ "+" + (const char*)itoa(index) + "\" class=\"dpid\">" + current->Dlang(i) + "</a>";
+ }
+ }
+ line += "</li>";
+ 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 <string>
+#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