summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthlo <t.lohmar@gmx.de>2013-01-05 16:37:48 +0100
committerthlo <t.lohmar@gmx.de>2013-01-05 16:37:48 +0100
commita84fb47821356729f6bbcf5813aec5f7c6faafed (patch)
treeca3b209b5179ce9e87a9b4371a50e7747a54fba6
parenta857c8fd7546029ddc666f835a0930f19a33be94 (diff)
downloadvdr-plugin-smarttvweb-a84fb47821356729f6bbcf5813aec5f7c6faafed.tar.gz
vdr-plugin-smarttvweb-a84fb47821356729f6bbcf5813aec5f7c6faafed.tar.bz2
New Web Front End. Various bug fixes.
-rw-r--r--README.txt9
-rw-r--r--vdr-smarttvweb/httpresource.c214
-rw-r--r--vdr-smarttvweb/httpresource.h15
-rw-r--r--vdr-smarttvweb/smarttvfactory.c19
-rw-r--r--vdr-smarttvweb/smarttvweb.c4
-rw-r--r--vdr-smarttvweb/smarttvweb.conf4
-rw-r--r--vdr-smarttvweb/stvw_cfg.c3
-rwxr-xr-xvdr-smarttvweb/web/Data.js247
-rwxr-xr-xvdr-smarttvweb/web/Server.js120
-rwxr-xr-xvdr-smarttvweb/web/favicon.icobin0 -> 1502 bytes
-rwxr-xr-xvdr-smarttvweb/web/index.html100
11 files changed, 584 insertions, 151 deletions
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..25cc16f
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,9 @@
+This VDR Project contains a server plugin and a Smart TV Widget.
+
+Folders:
+
+vdr-smarttvweb: Contains the vdr server plugin, incl. the web front end
+smarttv-client: Contains the source code of the Smart TV widget
+release: Contains Smart TV Widget files ready for deplyoment.
+
+Check http://projects.vdr-developer.org/projects/plg-smarttvweb/wiki for a description, installation instructions and configuration instructions.
diff --git a/vdr-smarttvweb/httpresource.c b/vdr-smarttvweb/httpresource.c
index 5f51197..1f4805a 100644
--- a/vdr-smarttvweb/httpresource.c
+++ b/vdr-smarttvweb/httpresource.c
@@ -304,7 +304,7 @@ int cHttpResource::processRequest() {
*(mLog->log())<< DEBUGPREFIX << " processRequest for mPath= " << mPath << DEBUGHDR << endl;
#endif
struct stat statbuf;
- // int ret = OKAY;
+ bool ok_to_serve = false;
if (mMethod.compare("POST")==0) {
return handlePost();
@@ -316,20 +316,6 @@ int cHttpResource::processRequest() {
}
#ifndef STANDALONE
- if (mPath.compare("/recordings.html") == 0) {
-#ifndef DEBUG
- *(mLog->log())<< DEBUGPREFIX
- << " generating /recordings.html"
- << DEBUGHDR << endl;
-#endif
- // ret = sendRecordingsHtml( &statbuf);
- if (handleHeadRequest() != 0)
- return OKAY;
-
- sendRecordingsHtml( &statbuf);
- return OKAY;
- }
-
if (mPath.compare("/recordings.xml") == 0) {
if (handleHeadRequest() != 0)
return OKAY;
@@ -377,6 +363,22 @@ int cHttpResource::processRequest() {
return sendFile(&statbuf);
}
+ if (mPath.compare("/favicon.ico") == 0) {
+ mPath = mFactory->getConfigDir() + "/web/favicon.ico";
+
+ if (stat(mPath.c_str(), &statbuf) < 0) {
+ sendError(404, "Not Found", NULL, "File not found.");
+ return OKAY;
+ }
+ if (handleHeadRequest() != 0)
+ return OKAY;
+
+ mFileSize = statbuf.st_size;
+ mContentType = SINGLEFILE;
+ return sendFile(&statbuf);
+ }
+
+
if (mPath.size() > 8) {
if (mPath.compare(mPath.size() -8, 8, "-seg.mpd") == 0) {
if (handleHeadRequest() != 0)
@@ -405,9 +407,20 @@ int cHttpResource::processRequest() {
}
}
+ if (mPath.find("/web/") == 0) {
+ mPath = mFactory->getConfigDir() + mPath;
+ *(mLog->log())<< DEBUGPREFIX
+ << " Found web request. serving " << mPath << endl;
+ ok_to_serve = true;
+ }
+
+
if (stat(mPath.c_str(), &statbuf) < 0) {
// checking, whether the file or directory exists
+ *(mLog->log())<< DEBUGPREFIX
+ << " File Not found " << mPath << endl;
sendError(404, "Not Found", NULL, "File not found.");
+
return OKAY;
}
@@ -429,12 +442,8 @@ int cHttpResource::processRequest() {
}
}
- if (mPath.compare(0, (mFactory->getConfig()->getMediaFolder()).size(), mFactory->getConfig()->getMediaFolder()) != 0) {
- // No directory access outside of MediaFolder
- *(mLog->log())<< DEBUGPREFIX
- << " Directory request is not for MediaFolde ("
- << mFactory->getConfig()->getMediaFolder() << ")"
- << endl;
+ if (!((ok_to_serve) or (mPath.compare(0, (mFactory->getConfig()->getMediaFolder()).size(), mFactory->getConfig()->getMediaFolder()) == 0))) {
+ // No directory access outside of MediaFolder (and also VDRCONG/web)
sendError(404, "Not Found", NULL, "File not found.");
return OKAY;
}
@@ -453,7 +462,7 @@ int cHttpResource::processRequest() {
#endif
// Check, if requested file is in Media Directory
- if (mPath.compare(0, (mFactory->getConfig()->getMediaFolder()).size(), mFactory->getConfig()->getMediaFolder()) != 0) {
+ if (!((ok_to_serve) or (mPath.compare(0, (mFactory->getConfig()->getMediaFolder()).size(), mFactory->getConfig()->getMediaFolder()) == 0))) {
sendError(404, "Not Found", NULL, "File not found.");
return OKAY;
}
@@ -529,8 +538,10 @@ int cHttpResource::fillDataBlk() {
return ERROR;
}
if (mRemLength == 0) {
+#ifndef DEBUG
*(mLog->log()) << DEBUGPREFIX << " mRemLength is zero "
<< "--> Done " << endl;
+#endif
fclose(mFile);
mFile = NULL;
return ERROR;
@@ -541,8 +552,10 @@ int cHttpResource::fillDataBlk() {
mRemLength -= mBlkLen;
if (mRemLength == 0) {
+#ifndef DEBUG
*(mLog->log()) << DEBUGPREFIX << " last Block read "
- << "--> Almost Done " << endl;
+ << "--> Almost Done " << endl;
+#endif
return OKAY;
}
@@ -553,6 +566,7 @@ int cHttpResource::fillDataBlk() {
snprintf(pathbuf, sizeof(pathbuf), mFileStructure.c_str(), mDir.c_str(), mVdrIdx);
mPath = pathbuf;
+
if (openFile(pathbuf) != OKAY) {
*(mLog->log())<< DEBUGPREFIX << " Failed to open file= " << pathbuf << " mRemLength= " << mRemLength<< endl;
mFile = NULL;
@@ -563,13 +577,13 @@ int cHttpResource::fillDataBlk() {
else
*(mLog->log()) << DEBUGPREFIX << " Still data to send mBlkLen= " << mBlkLen <<" --> continue " << endl;
return OKAY;
- }
+ } // Error: Open next file failed
if (mBlkLen == 0) {
to_read = ((mRemLength > MAXLEN) ? MAXLEN : mRemLength);
mBlkLen = fread(mBlkData, 1, to_read, mFile);
+ mRemLength -= mBlkLen;
}
- mRemLength -= mBlkLen;
}
break;
case SINGLEFILE:
@@ -933,9 +947,6 @@ int cHttpResource::getQueryAttributeValue(vector<sQueryAVP> *avps, string attr,
int cHttpResource::parseFiles(vector<sFileEntry> *entries, string prefix, string dir_base, string dir_name, struct stat *statbuf) {
char pathbuf[4096];
string link;
- // char f[400];
- // int len;
-
DIR *dir;
struct dirent *de;
string dir_comp;
@@ -963,6 +974,7 @@ int cHttpResource::parseFiles(vector<sFileEntry> *entries, string prefix, string
if (S_ISDIR(statbuf->st_mode)) {
if (strcmp(&(pathbuf[strlen(pathbuf)-4]), ".rec") == 0) {
+ // vdr folder
time_t now = time(NULL);
struct tm tm_r;
struct tm t = *localtime_r(&now, &tm_r);
@@ -984,6 +996,7 @@ int cHttpResource::parseFiles(vector<sFileEntry> *entries, string prefix, string
entries->push_back(sFileEntry(dir_name, pathbuf, start));
}
else {
+ // regular file
parseFiles(entries, prefix + de->d_name + "~", dir_comp, de->d_name, statbuf);
}
}
@@ -1004,12 +1017,11 @@ int cHttpResource::sendManifest (struct stat *statbuf, bool is_hls) {
string mpd_name = mPath.substr(pos+1);
float seg_dur = mFactory->getSegmentDuration() *1.0;
- // float seg_dur = SEGMENT_DURATION *1.0;
-
cRecordings* recordings = &Recordings;
cRecording* rec = recordings->GetByName(mDir.c_str());
- float duration = rec->NumFrames() / rec->FramesPerSecond();
+ double duration = rec->NumFrames() / rec->FramesPerSecond();
+
time_t now = time(NULL);
if (rec->Info() != NULL){
@@ -1032,6 +1044,7 @@ int cHttpResource::sendManifest (struct stat *statbuf, bool is_hls) {
// duration is now either the actual duration of the asset or the target duration of the asset
int end_seg = int (duration / seg_dur) +1;
+
// FIXME: Test Only
/* if (rec->FramesPerSecond() > 40)
end_seg = int (duration *2 / seg_dur) +1;
@@ -1056,7 +1069,7 @@ int cHttpResource::sendManifest (struct stat *statbuf, bool is_hls) {
return OKAY;
}
-void cHttpResource::writeM3U8(float duration, float seg_dur, int end_seg) {
+void cHttpResource::writeM3U8(double duration, float seg_dur, int end_seg) {
mResponseMessage = new string();
mResponseMessagePos = 0;
*mResponseMessage = "";
@@ -1067,32 +1080,32 @@ void cHttpResource::writeM3U8(float duration, float seg_dur, int end_seg) {
string hdr = "";
- sendHeaders(200, "OK", NULL, "application/x-mpegURL", -1, -1);
- *mResponseMessage += "#EXTM3U\n";
- // snprintf(buf, sizeof(buf), "#EXT-X-TARGETDURATION:%d\n", (seg_dur-1));
- snprintf(buf, sizeof(buf), "#EXT-X-TARGETDURATION:%d\n", int(seg_dur));
+ *mResponseMessage += "#EXTM3U\n";
+ // snprintf(buf, sizeof(buf), "#EXT-X-TARGETDURATION:%d\n", (seg_dur-1));
+ snprintf(buf, sizeof(buf), "#EXT-X-TARGETDURATION:%d\n", int(seg_dur));
+ hdr = buf;
+ *mResponseMessage += hdr;
+
+ *mResponseMessage += "#EXT-X-MEDIA-SEQUENCE:1\n";
+ *mResponseMessage += "#EXT-X-KEY:METHOD=NONE\n";
+
+ for (int i = 1; i < end_seg; i++){
+ // snprintf(buf, sizeof(buf), "#EXTINF:%.1f,\n", (seg_dur-0.5));
+ snprintf(buf, sizeof(buf), "#EXTINF:%.2f,\n", seg_dur);
hdr = buf;
*mResponseMessage += hdr;
-
- *mResponseMessage += "#EXT-X-MEDIA-SEQUENCE:1\n";
- *mResponseMessage += "#EXT-X-KEY:METHOD=NONE\n";
- for (int i = 1; i < end_seg; i++){
- // snprintf(buf, sizeof(buf), "#EXTINF:%.1f,\n", (seg_dur-0.5));
- snprintf(buf, sizeof(buf), "#EXTINF:%.2f,\n", seg_dur);
- hdr = buf;
- *mResponseMessage += hdr;
-
- snprintf(buf, sizeof(buf), "%d-seg.ts\n", i);
- hdr = buf;
- *mResponseMessage += hdr;
- }
- *mResponseMessage += "#EXT-X-ENDLIST\n";
+ snprintf(buf, sizeof(buf), "%d-seg.ts\n", i);
+ hdr = buf;
+ *mResponseMessage += hdr;
+ }
+ *mResponseMessage += "#EXT-X-ENDLIST\n";
+ sendHeaders(200, "OK", NULL, "application/x-mpegURL", mResponseMessage->size(), -1);
}
-void cHttpResource::writeMPD(float duration, float seg_dur, int end_seg) {
+void cHttpResource::writeMPD(double duration, float seg_dur, int end_seg) {
mResponseMessage = new string();
mResponseMessagePos = 0;
*mResponseMessage = "";
@@ -1104,20 +1117,12 @@ void cHttpResource::writeMPD(float duration, float seg_dur, int end_seg) {
string hdr = "";
- // sendHeaders(200, "OK", NULL, "application/xml", -1, -1);
-
*mResponseMessage += "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
snprintf(line, sizeof(line), "<MPD type=\"OnDemand\" minBufferTime=\"PT%dS\" mediaPresentationDuration=\"PT%.1fS\"",
mFactory->getHasMinBufferTime(), duration);
*mResponseMessage = *mResponseMessage + line;
- // *mResponseMessage += "<MPD type=\"OnDemand\" minBufferTime=\"PT30S\" mediaPresentationDuration=";
-
- // snprintf(buf, sizeof(buf), "\"PT%.1fS\"", duration);
- // hdr = buf;
-
- // *mResponseMessage += hdr + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:mpeg:mpegB:schema:DASH:MPD:DIS2011\" ";
*mResponseMessage += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:mpeg:mpegB:schema:DASH:MPD:DIS2011\" ";
*mResponseMessage += "xsi:schemaLocation=\"urn:mpeg:mpegB:schema:DASH:MPD:DIS2011\">\n";
*mResponseMessage += "<ProgramInformation>\n";
@@ -1127,9 +1132,6 @@ void cHttpResource::writeMPD(float duration, float seg_dur, int end_seg) {
snprintf(line, sizeof(line), "<Representation id=\"0\" mimeType=\"video/mpeg\" bandwidth=\"%d\" startWithRAP=\"True\" width=\"1280\" height=\"720\" group=\"0\">\n", mFactory->getHasBitrate());
*mResponseMessage = *mResponseMessage + line;
- // *mResponseMessage += "<Representation id=\"0\" mimeType=\"video/mpeg\" bandwidth=\"15000000\" startWithRAP=\"True\" width=\"1280\" height=\"720\" group=\"0\">\n";
-
- // *mResponseMessage += "<Representation id=\"0\" mimeType=\"video/mpeg\" bandwidth=\"5000000\" startWithRAP=\"True\" width=\"720\" height=\"576\" group=\"0\">\n";
hdr = "<SegmentInfo duration=";
snprintf(buf, sizeof(buf), "\"PT%.1fS\"", (seg_dur*1.0));
@@ -1146,7 +1148,7 @@ void cHttpResource::writeMPD(float duration, float seg_dur, int end_seg) {
*mResponseMessage += "</Period>\n";
*mResponseMessage += "</MPD>";
- sendHeaders(200, "OK", NULL, "application/xml", mResponseMessage->size(), -1);
+ sendHeaders(200, "OK", NULL, "application/x-mpegURL", mResponseMessage->size(), -1);
}
int cHttpResource::sendMediaSegment (struct stat *statbuf) {
@@ -1160,7 +1162,6 @@ int cHttpResource::sendMediaSegment (struct stat *statbuf) {
int seg_number;
int seg_dur = mFactory->getSegmentDuration();
-// int seg_dur = SEGMENT_DURATION;
int frames_per_seg = 0;
sscanf(seg_name.c_str(), "%d-seg.ts", &seg_number);
@@ -1262,9 +1263,11 @@ int cHttpResource::sendMediaSegment (struct stat *statbuf) {
#endif
}
else {
+#ifndef DEBUG
*(mLog->log()) << DEBUGPREFIX
<< " start_idx < end_idx "
<< endl;
+#endif
snprintf(seg_fn, sizeof(seg_fn), mFileStructure.c_str(), mDir.c_str(), mVdrIdx);
if (stat(seg_fn, statbuf) < 0) {
*(mLog->log()) << DEBUGPREFIX
@@ -1274,6 +1277,8 @@ int cHttpResource::sendMediaSegment (struct stat *statbuf) {
// issue:
}
rem_len = statbuf->st_size - start_offset; // remaining length of the first segment
+
+ // loop over all idx files between start_idx and end_idx
for (int idx = (start_idx+1); idx < end_idx; idx ++) {
snprintf(seg_fn, sizeof(seg_fn), mFileStructure.c_str(), mDir.c_str(), idx);
if (stat(seg_fn, statbuf) < 0) {
@@ -1288,9 +1293,13 @@ int cHttpResource::sendMediaSegment (struct stat *statbuf) {
}
rem_len += end_offset; //
mRemLength = rem_len;
+ snprintf(seg_fn, sizeof(seg_fn), mFileStructure.c_str(), mDir.c_str(), mVdrIdx);
+
+#ifndef DEBUG
*(mLog->log()) << DEBUGPREFIX
<< " start_idx= " << start_idx << " != end_idx= "<< end_idx <<": mRemLength= " <<mRemLength
<< endl;
+#endif
}
if (error){
@@ -1299,6 +1308,7 @@ int cHttpResource::sendMediaSegment (struct stat *statbuf) {
}
mContentType = VDRDIR;
+
if (openFile(seg_fn) != OKAY) {
*(mLog->log())<< DEBUGPREFIX << " Failed to open file= " << seg_fn
<< " mRemLength= " << mRemLength<< endl;
@@ -1576,7 +1586,7 @@ int cHttpResource::sendRecordingsXml(struct stat *statbuf) {
vector<sQueryAVP> avps;
parseQueryLine(&avps);
string model = "";
- string link_ext = "";
+ string link_ext = "";
string type = "";
string has_4_hd_str = "";
bool has_4_hd = true;
@@ -1638,11 +1648,12 @@ int cHttpResource::sendRecordingsXml(struct stat *statbuf) {
if (writeXmlItem("HLS - Big Bugs Bunny", "http://192.168.1.122/sm/BBB-DASH/HLS_BigBuckTS.m3u8|COMPONENT=HLS", "NA", "Big Bucks Bunny - HLS",
"-", 0, 0) == ERROR)
return ERROR;
+ if (writeXmlItem("HAS - Big Bugs Bunny", "http://192.168.1.122:8000/hd2/mpeg/BBB-DASH/HAS_BigBuckTS.xml|COMPONENT=HAS", "NA", "Big Bucks Bunny - HAS from own Server",
+ "-", 0, 0) == ERROR)
+ return ERROR;
*/
-
//--------------------
cRecordings* recordings = &Recordings;
- // char buff[20];
char f[600];
#ifndef DEBUG
@@ -1739,68 +1750,6 @@ int cHttpResource::sendRecordingsXml(struct stat *statbuf) {
return OKAY;
}
-int cHttpResource::sendRecordingsHtml(struct stat *statbuf) {
-#ifndef STANDALONE
-
- mResponseMessage = new string();
- mResponseMessagePos = 0;
- *mResponseMessage = "";
- mContentType = MEMBLOCK;
-
- mConnState = SERVING;
-
- vector<sQueryAVP> avps;
- parseQueryLine(&avps);
- string link_ext = "";
- string type = "";
-
- if (getQueryAttributeValue(&avps, "type", type) == OKAY){
- *(mLog->log())<< DEBUGPREFIX
- << " Found a Type Parameter: " << type
- << endl;
- if (type == "hls") {
- link_ext = "/manifest-seg.m3u8";
- }
- if (type == "has") {
- link_ext = "/manifest-seg.mpd";
- }
-
- }
-
- sendHeaders(200, "OK", NULL, "text/html", -1, statbuf->st_mtime);
-
- string hdr = "";
- hdr += "<HTML><HEAD><TITLE>Recordings</TITLE></HEAD>\r\n";
- hdr += "<meta http-equiv=\"content-type\" content=\"text/html;charset=UTF-8\"/>\r\n";
- hdr += "<BODY>";
- hdr += "<H4>Recordings</H4>\r\n<PRE>\n";
- hdr += "<HR>\r\n";
-
- *mResponseMessage += hdr;
-
- char buff[20];
- char f[400];
- for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
- hdr = "";
- time_t start_time = recording->Start();
- strftime(buff, 20, "%Y-%m-%d %H:%M:%S", localtime(&start_time));
-
- if (recording->IsPesRecording())
- snprintf(f, sizeof(f), "%s <A HREF=\"%s\">%s</A>\r\n", buff, cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str(),
- recording->Name());
- else
- snprintf(f, sizeof(f), "%s <A HREF=\"%s%s\">%s</A>\r\n", buff, cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str(),
- link_ext.c_str(), recording->Name());
-
- hdr += f;
- *mResponseMessage += hdr;
- // start is time_t
- }
-#endif
- return OKAY;
-}
-
-
int cHttpResource::sendVdrDir(struct stat *statbuf) {
#ifndef DEBUG
@@ -2074,17 +2023,20 @@ const char *cHttpResource::getMimeType(const char *name) {
if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg";
if (strcmp(ext, ".gif") == 0) return "image/gif";
if (strcmp(ext, ".png") == 0) return "image/png";
+ if (strcmp(ext, ".xml") == 0) return "application/xml";
if (strcmp(ext, ".css") == 0) return "text/css";
+ if (strcmp(ext, ".js") == 0) return "text/javascript";
if (strcmp(ext, ".au") == 0) return "audio/basic";
if (strcmp(ext, ".wav") == 0) return "audio/wav";
if (strcmp(ext, ".avi") == 0) return "video/x-msvideo";
if (strcmp(ext, ".mp4") == 0) return "video/mp4";
if (strcmp(ext, ".vdr") == 0) return "video/mpeg";
if (strcmp(ext, ".ts") == 0) return "video/mpeg";
- if (strcmp(ext, ".mpd") == 0) return "application/dash+xml";
- if (strcmp(ext, ".xml") == 0) return "application/xml";
if (strcmp(ext, ".mpeg") == 0 || strcmp(ext, ".mpg") == 0) return "video/mpeg";
if (strcmp(ext, ".mp3") == 0) return "audio/mpeg";
+ if (strcmp(ext, ".mpd") == 0) return "application/dash+xml";
+ if (strcmp(ext, ".m3u8") == 0) return "application/x-mpegURL";
+
return NULL;
}
diff --git a/vdr-smarttvweb/httpresource.h b/vdr-smarttvweb/httpresource.h
index 31148e2..79459b2 100644
--- a/vdr-smarttvweb/httpresource.h
+++ b/vdr-smarttvweb/httpresource.h
@@ -110,7 +110,6 @@ class cHttpResource {
int mBlkPos;
int mBlkLen;
- // string path;
string mRequest;
string mQuery;
string mPath;
@@ -131,9 +130,6 @@ class cHttpResource {
bool mIsRecording;
float mRecProgress;
- // int writeToClient(const char *buf, size_t buflen);
- // int sendDataChunk();
-
void setNonBlocking();
int fillDataBlk();
@@ -143,20 +139,17 @@ class cHttpResource {
int processHttpHeaderNew();
int readRequestPayload();
- // int processHttpHeader();
void sendError(int status, const char *title, const char *extra, const char *text);
int sendDir(struct stat *statbuf);
int sendVdrDir(struct stat *statbuf);
- int sendRecordingsHtml (struct stat *statbuf);
int sendRecordingsXml (struct stat *statbuf);
int sendChannelsXml (struct stat *statbuf);
int sendEpgXml (struct stat *statbuf);
int sendMediaXml (struct stat *statbuf);
- // int sendMPD (struct stat *statbuf);
int sendManifest (struct stat *statbuf, bool is_hls = true);
- void writeM3U8(float duration, float seg_dur, int end_seg);
- void writeMPD(float duration, float seg_dur, int end_seg);
+ void writeM3U8(double duration, float seg_dur, int end_seg);
+ void writeMPD(double duration, float seg_dur, int end_seg);
int sendMediaSegment (struct stat *statbuf);
@@ -181,9 +174,5 @@ class cHttpResource {
int getQueryAttributeValue(vector<sQueryAVP> *avps, string id, string &val);
int openFile(const char *name);
int writeXmlItem(string title, string link, string programme, string desc, string guid, time_t start, int dur);
- // string removeEtChar(string line);
- // string hexDump(string input);
- // string convertUrl(string input);
- // string convertBack(string input);
};
#endif
diff --git a/vdr-smarttvweb/smarttvfactory.c b/vdr-smarttvweb/smarttvfactory.c
index a57310a..b806021 100644
--- a/vdr-smarttvweb/smarttvfactory.c
+++ b/vdr-smarttvweb/smarttvfactory.c
@@ -1,7 +1,7 @@
/*
* smarttvfactory.h: VDR on Smart TV plugin
*
- * Copyright (C) 2012 Thorsten Lohmar
+ * Copyright (C) 2012 T. Lohmar
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -77,11 +77,28 @@ SmartTvServer::SmartTvServer(): mRequestCount(0), isInited(false), serverPort(PO
SmartTvServer::~SmartTvServer() {
+
if (mConfig != NULL)
delete mConfig;
}
void SmartTvServer::cleanUp() {
+ // close listening ports
+ for (uint idx= 0; idx < clientList.size(); idx++) {
+ if (clientList[idx] != NULL) {
+ close(idx);
+ delete clientList[idx];
+ clientList[idx] = NULL;
+ }
+ }
+
+ // close server port
+ close(mServerFd);
+
+ // Leave thread
+ pthread_cancel(mThreadId);
+ pthread_join(mThreadId, NULL);
+
mLog.shutdown();
}
diff --git a/vdr-smarttvweb/smarttvweb.c b/vdr-smarttvweb/smarttvweb.c
index b7c907a..34d87ee 100644
--- a/vdr-smarttvweb/smarttvweb.c
+++ b/vdr-smarttvweb/smarttvweb.c
@@ -1,7 +1,7 @@
/*
* smarttvweb.c: VDR on Smart TV plugin
*
- * Copyright (C) 2012 Thorsten Lohmar
+ * Copyright (C) 2012 T. Lohmar
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -41,7 +41,7 @@
#include "smarttvfactory.h"
-static const char *VERSION = "0.9.0";
+static const char *VERSION = "0.9.1";
static const char *DESCRIPTION = "SmartTV Web Server";
diff --git a/vdr-smarttvweb/smarttvweb.conf b/vdr-smarttvweb/smarttvweb.conf
index 6afbbb9..382f5bb 100644
--- a/vdr-smarttvweb/smarttvweb.conf
+++ b/vdr-smarttvweb/smarttvweb.conf
@@ -8,8 +8,8 @@ SegmentDuration 10
HasMinBufferTime 30
-HasBitrate 6000
+HasBitrate 60000000
LiveChannels 30
-UseHasForHd false \ No newline at end of file
+UseHasForHd false
diff --git a/vdr-smarttvweb/stvw_cfg.c b/vdr-smarttvweb/stvw_cfg.c
index 0c943b5..a7a5e58 100644
--- a/vdr-smarttvweb/stvw_cfg.c
+++ b/vdr-smarttvweb/stvw_cfg.c
@@ -1,7 +1,7 @@
/*
* stvw_cfg.h: VDR on Smart TV plugin
*
- * Copyright (C) 2012 Thorsten Lohmar
+ * Copyright (C) 2012 T. Lohmar
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -53,7 +53,6 @@ cSmartTvConfig::cSmartTvConfig(string d): mConfigDir(d), mLog(NULL), mCfgFile(NU
}
cSmartTvConfig::~cSmartTvConfig() {
- fclose(mCfgFile);
}
void cSmartTvConfig::readConfig() {
diff --git a/vdr-smarttvweb/web/Data.js b/vdr-smarttvweb/web/Data.js
new file mode 100755
index 0000000..5e480d2
--- /dev/null
+++ b/vdr-smarttvweb/web/Data.js
@@ -0,0 +1,247 @@
+var Data =
+{
+ assets : new Item,
+ folderList : [],
+};
+
+Data.reset = function() {
+ this.assets = null;
+ this.assets = new Item;
+
+ this.folderList = [];
+
+ this.folderList.push({item : this.assets, id: 0});
+};
+
+Data.completed= function(sort) {
+ if (sort == true)
+ this.assets.sortPayload();
+
+ this.folderList.push({item : this.assets, id: 0});
+ console.log ("Data.completed()= " +this.folderList.length);
+
+};
+
+Data.selectFolder = function (idx) {
+ this.folderList.push({item : this.getCurrentItem().childs[idx], id: idx});
+};
+
+Data.isRootFolder = function() {
+ if (this.folderList.length == 1)
+ return true;
+ else
+ return false;
+};
+
+Data.folderUp = function () {
+ itm = this.folderList.pop();
+ return itm.id;
+};
+
+Data.addItem = function(t_list, pyld) {
+ this.assets.addChild(t_list, pyld, 0);
+};
+
+Data.dumpFolderStruct = function(){
+ Main.log("---------- dumpFolderStruct ------------");
+ this.assets.print(0);
+ Main.log("---------- dumpFolderStruct Done -------");
+};
+
+Data.createDomTree = function () {
+
+ return this.assets.createDomTree(0);
+};
+
+Data.createJQMDomTree = function () {
+ return this.assets.createJQMDomTree(0);
+};
+
+Data.getCurrentItem = function () {
+ return this.folderList[this.folderList.length-1].item;
+};
+
+Data.getVideoCount = function()
+{
+ return this.folderList[this.folderList.length-1].item.childs.length;
+};
+
+Data.getNumString =function(num, fmt) {
+ var res = "";
+
+ if (num < 10) {
+ for (var i = 1; i < fmt; i ++) {
+ res += "0";
+ };
+ } else if (num < 100) {
+ for (var i = 2; i < fmt; i ++) {
+ res += "0";
+ };
+ }
+
+ res = res + num;
+
+ return res;
+};
+
+
+//-----------------------------------------
+function Item() {
+ this.title = "root";
+ this.isFolder = true;
+ this.childs = [];
+ this.payload = ""; // only set, if (isFolder == false)
+}
+
+Item.prototype.isFolder = function() {
+ return this.isFolder;
+};
+
+Item.prototype.getTitle = function () {
+ return this.title;
+};
+
+Item.prototype.getPayload = function () {
+ if (this.isFolder == true) {
+ Main.log("WARNING: getting payload on a folder title=" +this.title);
+ }
+ return this.payload;
+};
+
+Item.prototype.getItem = function (title) {
+ for (var i = 0; i < this.childs.length; i++) {
+ if (this.childs[i].title == title) {
+ return this.childs[i];
+ }
+ }
+ return 0;
+};
+
+Item.prototype.addChild = function (key, pyld, level) {
+ if (key.length == 1) {
+ var folder = new Item;
+// folder.title = pyld.startstr + " - " + key;
+ folder.title = key[0];
+ folder.payload = pyld;
+ folder.isFolder = false;
+ this.childs.push(folder);
+// this.titles.push({title: pyld.startstr + " - " + key , pyld : pyld});
+ }
+ else {
+ if (level > 10) {
+ Main.log(" too many levels");
+ return;
+ }
+ var t = key.shift();
+ var found = false;
+ for (var i = 0; i < this.childs.length; i++) {
+ if (this.childs[i].title == t) {
+ this.childs[i].addChild(key, pyld, level +1);
+ found = true;
+ break;
+ }
+ }
+ if (found == false) {
+ var folder = new Item;
+ folder.title = t;
+ folder.addChild(key, pyld, level+1);
+ this.childs.push(folder);
+ }
+ }
+};
+
+Item.prototype.print = function(level) {
+ var prefix= "";
+ for (var i = 0; i < level; i++)
+ prefix += " ";
+
+ for (var i = 0; i < this.childs.length; i++) {
+ Main.log(prefix + this.childs[i].title);
+ if (this.childs[i].isFolder == true) {
+ Main.log(prefix+"Childs:");
+ this.childs[i].print(level +1);
+ }
+ }
+};
+
+Item.prototype.createDomTree = function(level) {
+ var prefix= "";
+ for (var i = 0; i < level; i++)
+ prefix += "-";
+// var mydiv = $('<div class="folder">' +prefix+this.title+ '</div>');
+ var mydiv = $('<ul />');
+
+ for (var i = 0; i < this.childs.length; i++) {
+ if (this.childs[i].isFolder == true) {
+// mydiv.appendChild(this.childs[i].createDomTree());
+ var myli = $('<li class="folder">' +prefix +this.childs[i].title + '</li>');
+ myli.append(this.childs[i].createDomTree(level+1));
+ mydiv.append(myli);
+ }
+ else {
+// var mya = $('<a class="link">' +prefix +this.childs[i].title + '</a>');
+ var mya = $('<a>', { text: prefix +this.childs[i].title, href: this.childs[i].payload['link']} );
+ var myli = $('<li class="item"/>');
+ myli.append(mya);
+ mydiv.append(myli);
+ }
+ }
+ return mydiv;
+};
+
+Item.prototype.createJQMDomTree = function(level) {
+ var mydiv = $('<ul />');
+// if (level == 0) {
+ mydiv.attr('data-role', 'listview');
+ mydiv.attr('data-inset', 'true');
+// };
+//, id:'dyncreated'
+ for (var i = 0; i < this.childs.length; i++) {
+ if (this.childs[i].isFolder == true) {
+ var myh = $('<div>', {text: this.childs[i].title});
+ var myli = $('<li>');
+ myli.append(myh);
+ var mycount = $('<p>', {class: 'ui-li-count', text: this.childs[i].childs.length});
+ myli.append(mycount);
+ myli.append(this.childs[i].createJQMDomTree(level+1));
+ mydiv.append(myli);
+ }
+ else {
+ // Links
+ var digi = new Date(this.childs[i].payload['start'] *1000);
+ var mon = Data.getNumString ((digi.getMonth()+1), 2);
+ var day = Data.getNumString (digi.getDate(), 2);
+ var hour = Data.getNumString (digi.getHours(), 2);
+ var min = Data.getNumString (digi.getMinutes(), 2);
+
+ var d_str = mon + "/" + day + " " + hour + ":" + min;
+ var mya = $('<a>', { text: d_str + " - " + this.childs[i].title,
+ href: this.childs[i].payload['link'],
+ rel: 'external'} );
+ var myli = $('<li class="item"/>');
+ myli.attr('data-icon', 'false');
+ myli.attr('data-theme', 'c');
+
+ myli.append(mya);
+ mydiv.append(myli);
+ }
+ }
+ return mydiv;
+};
+
+Item.prototype.sortPayload = function() {
+ for (var i = 0; i < this.childs.length; i++) {
+ if (this.childs[i].isFolder == true) {
+ this.childs[i].sortPayload();
+ }
+ }
+ this.childs.sort(function(a,b) {
+ if (a.title == b.title) {
+ return (b.payload.start - a.payload.start);
+ }
+ else {
+ return ((a.title < b.title) ? -1 : 1);
+ }
+ });
+};
+
diff --git a/vdr-smarttvweb/web/Server.js b/vdr-smarttvweb/web/Server.js
new file mode 100755
index 0000000..9fb39c5
--- /dev/null
+++ b/vdr-smarttvweb/web/Server.js
@@ -0,0 +1,120 @@
+var Server =
+{
+ dataReceivedCallback : null,
+ errorCallback : null,
+ doSort : false,
+ retries : 0,
+
+ XHRObj : null
+};
+
+Server.init = function()
+{
+ var success = true;
+
+ if (this.XHRObj) {
+ this.XHRObj.destroy();
+ this.XHRObj = null;
+ }
+
+ return success;
+};
+
+Server.setSort = function (val) {
+ this.doSort = val;
+};
+
+Server.fetchVideoList = function(url) {
+ if (this.XHRObj == null) {
+ this.XHRObj = new XMLHttpRequest();
+ }
+
+ if (this.XHRObj) {
+ this.XHRObj.onreadystatechange = function()
+ {
+ var splashElement = document.getElementById("splashStatus");
+
+ if (Server.XHRObj.readyState == 4) {
+ Server.createVideoList();
+ }
+ };
+
+ this.XHRObj.open("GET", url, true);
+ this.XHRObj.send(null);
+ }
+ else {
+ console.log("Failed to create XHR");
+
+ if (this.errorCallback != null) {
+ this.errorCallback("ServerError");
+ }
+ }
+};
+
+Server.createVideoList = function() {
+ console.log ("creating Video list now");
+
+ if (this.XHRObj.status != 200) {
+ if (this.errorCallback != null) {
+ this.errorCallback("ServerError");
+ }
+ }
+ else
+ {
+ var xmlResponse = this.XHRObj.responseXML;
+ if (xmlResponse == null) {
+ if (this.errorCallback != null) {
+ this.errorCallback("XmlError");
+ }
+ return;
+ }
+ var xmlElement = xmlResponse.documentElement;
+
+ if (!xmlElement) {
+ console.log("Failed to get valid XML");
+ return;
+ }
+ else
+ {
+ var items = xmlElement.getElementsByTagName("item");
+ if (items.length == 0) {
+ console.log("Something wrong. Response does not contain any item");
+ };
+
+ for (var index = 0; index < items.length; index++) {
+
+ var titleElement = items[index].getElementsByTagName("title")[0];
+ var progElement = items[index].getElementsByTagName("programme")[0];
+ var descriptionElement = items[index].getElementsByTagName("description")[0];
+ var linkElement = items[index].getElementsByTagName("link")[0];
+ var startVal =0;
+ var durVal =0;
+ try {
+ startVal = parseInt(items[index].getElementsByTagName("start")[0].firstChild.data);
+ durVal = parseInt(items[index].getElementsByTagName("duration")[0].firstChild.data);
+ }
+ catch (e) {
+ console.log("ERROR: "+e);
+ }
+
+ var desc = descriptionElement.firstChild.data;
+
+ if (titleElement && linkElement) {
+ var title_list = titleElement.firstChild.data.split("~");
+ Data.addItem( title_list, {link : linkElement.firstChild.data,
+ prog: progElement.firstChild.data,
+ desc: desc,
+ start: startVal,
+ dur: durVal});
+ }
+
+ }
+ Data.completed(this.doSort);
+
+ if (this.dataReceivedCallback)
+ {
+ this.dataReceivedCallback(); /* Notify all data is received and stored */
+ }
+ }
+ }
+};
diff --git a/vdr-smarttvweb/web/favicon.ico b/vdr-smarttvweb/web/favicon.ico
new file mode 100755
index 0000000..2236569
--- /dev/null
+++ b/vdr-smarttvweb/web/favicon.ico
Binary files differ
diff --git a/vdr-smarttvweb/web/index.html b/vdr-smarttvweb/web/index.html
new file mode 100755
index 0000000..9e1117b
--- /dev/null
+++ b/vdr-smarttvweb/web/index.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
+<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
+<script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
+<script language="javascript" type="text/javascript" src="Server.js"></script>
+<script language="javascript" type="text/javascript" src="Data.js"></script>
+<script>
+$(document).ready(function(){
+ var state = 'rec'; // Rec
+
+ Server.init();
+
+ Server.dataReceivedCallback = function() {
+ console.log("Loaded");
+ $("#anchor").append(Data.createJQMDomTree()).trigger('create');
+ $.mobile.loading('hide');
+ //http://jquerymobile.com/
+
+ // end of dataReceivedCallBack
+ };
+
+ $.mobile.loading('show');
+ Server.setSort(true);
+ Server.fetchVideoList("/recordings.xml");
+
+ removeDomTree = function () {
+ // parent should not be deleted.
+ $("#anchor").children().remove();
+ };
+
+ buttonHandler = function(btn) {
+ console.log("Click: " + btn);
+ if (state == btn) {
+ console.log("No Change");
+ };
+ $.mobile.loading('show');
+ Data.reset();
+ removeDomTree();
+ switch (btn) {
+ case 'rec':
+ state = 'rec';
+ $('#recbtn').addClass('ui-btn-active');
+ $('#medbtn').removeClass('ui-btn-active');
+ $('#chnbtn').removeClass('ui-btn-active');
+ Server.setSort(true);
+ Server.fetchVideoList("/recordings.xml");
+ break;
+ case 'med':
+ state = 'med';
+ $('#medbtn').addClass('ui-btn-active');
+ $('#recbtn').removeClass('ui-btn-active');
+ $('#chnbtn').removeClass('ui-btn-active');
+ Server.setSort(true);
+ Server.fetchVideoList("/media.xml");
+ break;
+ case 'chn':
+ state = 'chn';
+ $('#chnbtn').addClass('ui-btn-active');
+ $('#medbtn').removeClass('ui-btn-active');
+ $('#recbtn').removeClass('ui-btn-active');
+ Server.setSort(false);
+ Server.fetchVideoList("/channels.xml");
+ break;
+ };
+ };
+
+});
+</script>
+<style type="text/css" >
+h2 { margin:1.2em 0 .4em 0; }
+ }
+</style>
+</head>
+
+<body>
+ <div data-role="page" class="type-interior">
+
+ <div data-role="header" data-theme="b">
+ <h1>Recordings</h1>
+ </div> <!-- /header -->
+
+ <div data-role="content">
+ <div class="content-primary" >
+
+ <div data-role="controlgroup" data-type="horizontal" data-mini="true">
+ <a href="#" id="recbtn" data-role="button" data-transition="fade" class="ui-btn-active" onclick="return buttonHandler('rec')">Recordings</a>
+ <a href="#" id="medbtn" data-role="button" data-transition="fade" onclick="return buttonHandler('med')">Media</a>
+ <a href="#" id="chnbtn" data-role="button" data-transition="fade" onclick="return buttonHandler('chn')">Channels</a>
+ </div>
+
+ <div id="anchor"/>
+ </div>
+ </div>
+</div>
+ </body>
+</html>
+