diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/connectionHTTP.c | 198 | ||||
-rw-r--r-- | server/connectionHTTP.h | 7 | ||||
-rw-r--r-- | server/connectionVTP.c | 26 | ||||
-rw-r--r-- | server/connectionVTP.h | 1 | ||||
-rw-r--r-- | server/livestreamer.c | 48 | ||||
-rw-r--r-- | server/livestreamer.h | 3 | ||||
-rw-r--r-- | server/menuHTTP.c | 420 | ||||
-rw-r--r-- | server/menuHTTP.h | 140 |
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 |