Streamdev 0.3.4

This commit is contained in:
Frank Schmirler
2010-12-02 09:02:31 +01:00
parent 7576173547
commit 31df0eaf8e
21 changed files with 1064 additions and 194 deletions

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

@@ -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");

View File

@@ -24,6 +24,7 @@ private:
char *m_LastCommand;
eStreamType m_StreamType;
bool m_FiltersSupport;
// Members adopted for SVDRP
cRecordings Recordings;

View File

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

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

420
server/menuHTTP.c Normal file
View File

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

140
server/menuHTTP.h Normal file
View File

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