summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorFrank Schmirler <vdr@schmirler.de>2012-12-16 13:29:15 +0100
committerFrank Schmirler <vdr@schmirler.de>2012-12-16 13:29:15 +0100
commit9135cde7121b72c24907752e64c519696d38154e (patch)
treef0033d7c4bb309f79f56bbdb1e99881622caeaff /server
parent0cf406ed3ab2ab465787f5a425dec8d7d705903e (diff)
downloadvdr-plugin-streamdev-9135cde7121b72c24907752e64c519696d38154e.tar.gz
vdr-plugin-streamdev-9135cde7121b72c24907752e64c519696d38154e.tar.bz2
Basic support for HTTP streaming of recordings
Diffstat (limited to 'server')
-rw-r--r--server/Makefile2
-rw-r--r--server/connectionHTTP.c105
-rw-r--r--server/connectionHTTP.h21
-rw-r--r--server/livestreamer.h2
-rw-r--r--server/recstreamer.c67
-rw-r--r--server/recstreamer.h32
-rw-r--r--server/streamer.h2
7 files changed, 211 insertions, 20 deletions
diff --git a/server/Makefile b/server/Makefile
index ba09649..00c471c 100644
--- a/server/Makefile
+++ b/server/Makefile
@@ -22,7 +22,7 @@ SERVEROBJS = $(PLUGIN).o \
componentVTP.o connectionVTP.o \
componentHTTP.o connectionHTTP.o menuHTTP.o \
componentIGMP.o connectionIGMP.o \
- streamer.o livestreamer.o livefilter.o recplayer.o \
+ streamer.o livestreamer.o livefilter.o recstreamer.o recplayer.o \
menu.o suspend.o setup.o
### The main target:
diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c
index b7fcfa8..b37255a 100644
--- a/server/connectionHTTP.c
+++ b/server/connectionHTTP.c
@@ -5,6 +5,8 @@
#include <ctype.h>
#include <time.h>
#include <stdarg.h>
+#include <vdr/thread.h>
+#include <vdr/recording.h>
#include "server/connectionHTTP.h"
#include "server/menuHTTP.h"
@@ -14,9 +16,10 @@
cConnectionHTTP::cConnectionHTTP(void):
cServerConnection("HTTP"),
m_Status(hsRequest),
- m_LiveStreamer(NULL),
- m_Channel(NULL),
+ m_Streamer(NULL),
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
+ m_Channel(NULL),
+ m_Recording(NULL),
m_ChannelList(NULL)
{
Dprintf("constructor hsRequest\n");
@@ -26,7 +29,8 @@ cConnectionHTTP::cConnectionHTTP(void):
cConnectionHTTP::~cConnectionHTTP()
{
- delete m_LiveStreamer;
+ delete m_Streamer;
+ delete m_Recording;
}
bool cConnectionHTTP::CanAuthenticate(void)
@@ -168,9 +172,10 @@ bool cConnectionHTTP::ProcessRequest(void)
device = GetDevice(m_Channel, StreamdevServerSetup.HTTPPriority);
if (device != NULL) {
device->SwitchChannel(m_Channel, false);
- m_LiveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this);
- if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) {
- m_LiveStreamer->SetDevice(device);
+ cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this);
+ m_Streamer = liveStreamer;
+ if (liveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) {
+ liveStreamer->SetDevice(device);
if (!SetDSCP())
LOG_ERROR_STR("unable to set DSCP sockopt");
if (m_StreamType == stEXT) {
@@ -183,10 +188,26 @@ bool cConnectionHTTP::ProcessRequest(void)
return HttpResponse(200, false, "video/mpeg");
}
}
- DELETENULL(m_LiveStreamer);
+ DELETENULL(m_Streamer);
}
return HttpResponse(503, true);
}
+ else if (m_Recording != NULL) {
+ Dprintf("GET recording\n");
+ cStreamdevRecStreamer* recStreamer = new cStreamdevRecStreamer(m_Recording, this);
+ m_Streamer = recStreamer;
+ int64_t from, to;
+ uint64_t total = recStreamer->GetLength();
+ if (ParseRange(from, to)) {
+ int64_t length = recStreamer->SetRange(from, to);
+ if (length < 0L)
+ return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
+ else
+ return HttpResponse(206, false, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length);
+ }
+ else
+ return HttpResponse(200, false, "video/mpeg", "Accept-Ranges: bytes");
+ }
else {
return HttpResponse(404, true);
}
@@ -198,8 +219,9 @@ bool cConnectionHTTP::ProcessRequest(void)
else if (m_Channel != NULL) {
if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) {
if (m_StreamType == stEXT) {
- m_LiveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this);
- m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
+ cStreamdevLiveStreamer *liveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this);
+ liveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
+ m_Streamer = liveStreamer;
return Respond("HTTP/1.0 200 OK");
} else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) {
return HttpResponse(200, true, "audio/mpeg", "icy-name: %s", m_Channel->Name());
@@ -211,6 +233,22 @@ bool cConnectionHTTP::ProcessRequest(void)
}
return HttpResponse(503, true);
}
+ else if (m_Recording != NULL) {
+ Dprintf("HEAD recording\n");
+ cStreamdevRecStreamer *recStreamer = new cStreamdevRecStreamer(m_Recording, this);
+ m_Streamer = recStreamer;
+ int64_t from, to;
+ uint64_t total = recStreamer->GetLength();
+ if (ParseRange(from, to)) {
+ int64_t length = recStreamer->SetRange(from, to);
+ if (length < 0L)
+ return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
+ else
+ return HttpResponse(206, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length);
+ }
+ else
+ return HttpResponse(200, true, "video/mpeg", "Accept-Ranges: bytes");
+ }
else {
return HttpResponse(404, true);
}
@@ -244,9 +282,11 @@ bool cConnectionHTTP::HttpResponse(int Code, bool Last, const char* ContentType,
switch (Code)
{
case 200: rc = Respond("HTTP/1.1 200 OK"); break;
+ case 206: rc = Respond("HTTP/1.1 206 Partial Content"); break;
case 400: rc = Respond("HTTP/1.1 400 Bad Request"); break;
case 401: rc = Respond("HTTP/1.1 401 Authorization Required"); break;
case 404: rc = Respond("HTTP/1.1 404 Not Found"); break;
+ case 416: rc = Respond("HTTP/1.1 416 Requested range not satisfiable"); break;
case 503: rc = Respond("HTTP/1.1 503 Service Unavailable"); break;
default: rc = Respond("HTTP/1.1 500 Internal Server Error");
}
@@ -279,6 +319,40 @@ bool cConnectionHTTP::HttpResponse(int Code, bool Last, const char* ContentType,
return rc && Respond("");
}
+bool cConnectionHTTP::ParseRange(int64_t &From, int64_t &To) const
+{
+ const static std::string RANGE("HTTP_RANGE");
+ From = To = 0L;
+ tStrStrMap::const_iterator it = Headers().find(RANGE);
+ if (it != Headers().end()) {
+ size_t b = it->second.find("bytes=");
+ if (b != std::string::npos) {
+ char* e = NULL;
+ const char* r = it->second.c_str() + b + sizeof("bytes=") - 1;
+ if (strchr(r, ',') != NULL)
+ esyslog("streamdev-server cConnectionHTTP::GetRange: Multi-ranges not supported");
+ From = strtol(r, &e, 10);
+ if (r != e) {
+ if (From < 0L) {
+ To = -1L;
+ return *e == 0 || *e == ',';
+ }
+ else if (*e == '-') {
+ r = e + 1;
+ if (*r == 0 || *e == ',') {
+ To = -1L;
+ return true;
+ }
+ To = strtol(r, &e, 10);
+ return r != e && To >= From &&
+ (*e == 0 || *e == ',');
+ }
+ }
+ }
+ }
+ return false;
+}
+
void cConnectionHTTP::Flushed(void)
{
if (m_Status != hsBody)
@@ -296,9 +370,9 @@ void cConnectionHTTP::Flushed(void)
}
return;
}
- else if (m_Channel != NULL) {
+ else if (m_Streamer != NULL) {
Dprintf("streamer start\n");
- m_LiveStreamer->Start(this);
+ m_Streamer->Start(this);
m_Status = hsFinished;
}
else {
@@ -401,6 +475,13 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
if ((m_ChannelList = ChannelListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
Dprintf("Channel list requested\n");
return true;
+ } else if (strcmp(fileext.c_str(), ".rec") == 0) {
+ cThreadLock RecordingsLock(&Recordings);
+ cRecording* rec = Recordings.Get(atoi(filespec.c_str()) - 1);
+ Dprintf("Recording %s%s found\n", rec ? rec->Name() : filespec.c_str(), rec ? "" : " not");
+ if (rec)
+ m_Recording = new cRecording(rec->FileName());
+ return m_Recording != NULL;
} else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) {
Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]);
return true;
@@ -411,5 +492,5 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
cString cConnectionHTTP::ToText() const
{
cString str = cServerConnection::ToText();
- return m_LiveStreamer ? cString::sprintf("%s\t%s", *str, *m_LiveStreamer->ToText()) : str;
+ return m_Streamer ? cString::sprintf("%s\t%s", *str, *m_Streamer->ToText()) : str;
}
diff --git a/server/connectionHTTP.h b/server/connectionHTTP.h
index b5b5b86..2fb8b07 100644
--- a/server/connectionHTTP.h
+++ b/server/connectionHTTP.h
@@ -7,12 +7,12 @@
#include "connection.h"
#include "server/livestreamer.h"
+#include "server/recstreamer.h"
#include <map>
#include <tools/select.h>
class cChannel;
-class cStreamdevLiveStreamer;
class cChannelList;
class cConnectionHTTP: public cServerConnection {
@@ -27,12 +27,14 @@ private:
std::string m_Authorization;
eHTTPStatus m_Status;
tStrStrMap m_Params;
+ cStreamdevStreamer *m_Streamer;
+ eStreamType m_StreamType;
// job: transfer
- cStreamdevLiveStreamer *m_LiveStreamer;
const cChannel *m_Channel;
int m_Apid[2];
int m_Dpid[2];
- eStreamType m_StreamType;
+ // job: replay
+ cRecording *m_Recording;
// job: listing
cChannelList *m_ChannelList;
@@ -40,6 +42,13 @@ private:
bool ProcessURI(const std::string &PathInfo);
bool HttpResponse(int Code, bool Last, const char* ContentType = NULL, const char* Headers = "", ...);
//__attribute__ ((format (printf, 5, 6)));
+ /**
+ * Extract byte range from HTTP Range header. Returns false if no valid
+ * range is found. The contents of From and To are undefined in this
+ * case. From may be negative in which case To is undefined.
+ * TODO: support for multiple ranges.
+ */
+ bool ParseRange(int64_t &From, int64_t &To) const;
protected:
bool ProcessRequest(void);
@@ -47,8 +56,8 @@ public:
cConnectionHTTP(void);
virtual ~cConnectionHTTP();
- virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
- virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
+ virtual void Attach(void) { if (m_Streamer != NULL) m_Streamer->Attach(); }
+ virtual void Detach(void) { if (m_Streamer != NULL) m_Streamer->Detach(); }
virtual cString ToText() const;
@@ -62,7 +71,7 @@ public:
inline bool cConnectionHTTP::Abort(void) const
{
- return !IsOpen() || (m_LiveStreamer && m_LiveStreamer->Abort());
+ return !IsOpen() || (m_Streamer && m_Streamer->Abort());
}
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
diff --git a/server/livestreamer.h b/server/livestreamer.h
index 8c7fc88..db546ec 100644
--- a/server/livestreamer.h
+++ b/server/livestreamer.h
@@ -49,7 +49,7 @@ public:
bool SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL);
void SetPriority(int Priority);
void GetSignal(int *DevNum, int *Strength, int *Quality) const;
- cString ToText() const;
+ virtual cString ToText() const;
void Receive(uchar *Data, int Length);
virtual bool IsReceiving(void) const;
diff --git a/server/recstreamer.c b/server/recstreamer.c
new file mode 100644
index 0000000..73af53b
--- /dev/null
+++ b/server/recstreamer.c
@@ -0,0 +1,67 @@
+#include "remux/ts2ps.h"
+#include "remux/ts2pes.h"
+#include "remux/ts2es.h"
+#include "remux/extern.h"
+
+#include <vdr/ringbuffer.h>
+#include "server/recstreamer.h"
+#include "server/connection.h"
+#include "common.h"
+
+using namespace Streamdev;
+
+// --- cStreamdevRecStreamer -------------------------------------------------
+
+cStreamdevRecStreamer::cStreamdevRecStreamer(cRecording *Rec, const cServerConnection *Connection):
+ cStreamdevStreamer("streamdev-recstreaming", Connection),
+ m_RecPlayer(Rec),
+ m_From(0L)
+{
+ Dprintf("New rec streamer\n");
+ m_To = (int64_t) m_RecPlayer.getLengthBytes() - 1;
+}
+
+cStreamdevRecStreamer::~cStreamdevRecStreamer()
+{
+ Dprintf("Desctructing rec streamer\n");
+ Stop();
+}
+
+int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To)
+{
+ int64_t l = (int64_t) m_RecPlayer.getLengthBytes();
+ if (From < 0L) {
+ From += l;
+ if (From < 0L)
+ From = 0L;
+ To = l - 1;
+ }
+ else {
+ if (To < 0L)
+ To += l;
+ else if (To >= l)
+ To = l - 1;
+ if (From > To) {
+ // invalid range - return whole content
+ From = 0L;
+ To = l - 1;
+ }
+ }
+ m_From = From;
+ m_To = To;
+ return m_To - m_From + 1;
+}
+
+uchar* cStreamdevRecStreamer::GetFromReceiver(int &Count)
+{
+ if (m_From <= m_To) {
+ Count = (int) m_RecPlayer.getBlock(m_Buffer, m_From, sizeof(m_Buffer));
+ return m_Buffer;
+ }
+ return NULL;
+}
+
+cString cStreamdevRecStreamer::ToText() const
+{
+ return "REPLAY";
+}
diff --git a/server/recstreamer.h b/server/recstreamer.h
new file mode 100644
index 0000000..83df8c1
--- /dev/null
+++ b/server/recstreamer.h
@@ -0,0 +1,32 @@
+#ifndef VDR_STREAMDEV_RECSTREAMER_H
+#define VDR_STREAMDEV_RECSTREAMER_H
+
+#include "server/streamer.h"
+#include "server/recplayer.h"
+
+#define RECBUFSIZE (174 * TS_SIZE)
+
+// --- cStreamdevRecStreamer -------------------------------------------------
+
+class cStreamdevRecStreamer: public cStreamdevStreamer {
+private:
+ //Streamdev::cTSRemux *m_Remux;
+ RecPlayer m_RecPlayer;
+ int64_t m_From;
+ int64_t m_To;
+ uchar m_Buffer[RECBUFSIZE];
+
+protected:
+ virtual uchar* GetFromReceiver(int &Count);
+ virtual void DelFromReceiver(int Count) { m_From += Count; };
+
+public:
+ virtual bool IsReceiving(void) const { return m_From <= m_To; };
+ inline uint64_t GetLength() { return m_RecPlayer.getLengthBytes(); }
+ int64_t SetRange(int64_t &From, int64_t &To);
+ virtual cString ToText() const;
+ cStreamdevRecStreamer(cRecording *Recording, const cServerConnection *Connection);
+ virtual ~cStreamdevRecStreamer();
+};
+
+#endif // VDR_STREAMDEV_RECSTREAMER_H
diff --git a/server/streamer.h b/server/streamer.h
index 74c9524..d9b2998 100644
--- a/server/streamer.h
+++ b/server/streamer.h
@@ -92,6 +92,8 @@ public:
virtual void Detach(void) {}
virtual void Attach(void) {}
+
+ virtual cString ToText() const { return ""; };
};
inline bool cStreamdevStreamer::Abort(void)