summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrandomdan <randomdan@localhost>2014-03-20 23:56:18 +0000
committerrandomdan <randomdan@localhost>2014-03-20 23:56:18 +0000
commitc6df1ea3d9f4ac3ffa85c3eefc3f30a84df6f8d7 (patch)
treef45b31a3ea600f6370200432256e1ba8147b4064
parentRemove the pointless maint functions for program map and program associations... (diff)
downloadp2pvr-c6df1ea3d9f4ac3ffa85c3eefc3f30a84df6f8d7.tar.bz2
p2pvr-c6df1ea3d9f4ac3ffa85c3eefc3f30a84df6f8d7.tar.xz
p2pvr-c6df1ea3d9f4ac3ffa85c3eefc3f30a84df6f8d7.zip
Basic support for streaming services (slow, thanks ffmpeg) and recordings (assumes storage endpoint) through the web frontend - implemented as two project2 stream sources
-rw-r--r--p2pvr/.p2config2
-rw-r--r--p2pvr/Jamfile.jam14
-rw-r--r--p2pvr/daemon/recorder.cpp2
-rw-r--r--p2pvr/daemon/storage.cpp44
-rw-r--r--p2pvr/daemon/storage.h2
-rw-r--r--p2pvr/ice/p2pvr.ice2
-rw-r--r--p2pvr/lib/serviceStreamer.cpp12
-rw-r--r--p2pvr/lib/serviceStreamer.h1
-rw-r--r--p2pvr/p2comp/Jamfile.jam10
-rw-r--r--p2pvr/p2comp/recordingStream.cpp80
-rw-r--r--p2pvr/p2comp/serviceStream.cpp58
-rw-r--r--p2pvr/p2comp/streamBase.cpp50
-rw-r--r--p2pvr/p2comp/streamBase.h26
-rw-r--r--p2pvr/p2comp/streamSinkWrapper.cpp28
-rw-r--r--p2pvr/p2comp/streamSinkWrapper.h23
-rw-r--r--p2pvr/streamer/Jamfile.jam7
-rw-r--r--p2pvr/streamer/streamer.cpp52
-rw-r--r--p2pvr/webfe/.htaccess12
-rw-r--r--p2pvr/webfe/.p2config1
l---------p2pvr/webfe/p2fcgi1
-rw-r--r--p2pvr/webfe/present/stream/recording.xml8
-rw-r--r--p2pvr/webfe/present/stream/service.xml8
22 files changed, 379 insertions, 64 deletions
diff --git a/p2pvr/.p2config b/p2pvr/.p2config
index ddf998e..8e215ad 100644
--- a/p2pvr/.p2config
+++ b/p2pvr/.p2config
@@ -5,4 +5,4 @@ common.filelog.path = /tmp/p2daemon.log
common.filelog.openmode = w
common.consolelogLevel = 9
p2pvr.globaldevices.carddaemon = Devices:default -h defiant -p 10000
-p2pvr.storage.root = /tmp/p2pvr/recordings
+p2pvr.storage.root = /var/store/p2pvr/recordings
diff --git a/p2pvr/Jamfile.jam b/p2pvr/Jamfile.jam
index 9a4baac..bee95cd 100644
--- a/p2pvr/Jamfile.jam
+++ b/p2pvr/Jamfile.jam
@@ -9,6 +9,10 @@ alias p2common : glibmm : : :
<include>/usr/include/project2/lib
<linkflags>"-lp2common"
;
+alias p2cgi : glibmm : : :
+ <include>/usr/include/project2/cgi
+ <linkflags>"-lp2cgicommon"
+;
alias p2sql : glibmm : : :
<include>/usr/include/project2/sql
<linkflags>"-lp2sql"
@@ -17,9 +21,13 @@ alias p2lib : glibmm : : :
<include>/usr/include/project2/lib
<linkflags>"-lp2lib"
;
+alias p2streams : glibmm : : :
+ <include>/usr/include/project2/streams
+ <linkflags>"-lp2streams"
+;
alias p2ice : glibmm : : :
<include>/usr/include/project2/ice
- <linkflags>"-lp2ice"
+ <linkflags>"-lp2ice -lp2iceclient"
;
alias p2daemonlib : glibmm : : :
<cflags>"-I /usr/include/project2/daemon/lib"
@@ -28,8 +36,8 @@ alias p2daemonlib : glibmm : : :
build-project daemon ;
build-project carddaemon ;
-install debuginstall : dvb//p2pvrdvb devices//p2pvrdevices lib//p2pvrlib carddaemon//p2pvrcarddaemon daemonbase//p2pvrdaemonbase daemon//p2pvrdaemon ice//p2pvrice : <location>./testing ;
-package.install install : : : carddaemon daemon ;
+install debuginstall : p2comp//p2pvrp2comp dvb//p2pvrdvb devices//p2pvrdevices lib//p2pvrlib carddaemon//p2pvrcarddaemon daemonbase//p2pvrdaemonbase daemon//p2pvrdaemon ice//p2pvrice : <location>./testing ;
+package.install install : : : p2comp carddaemon daemon ;
import type ;
import generators ;
diff --git a/p2pvr/daemon/recorder.cpp b/p2pvr/daemon/recorder.cpp
index bdd18c6..ccb975e 100644
--- a/p2pvr/daemon/recorder.cpp
+++ b/p2pvr/daemon/recorder.cpp
@@ -16,7 +16,7 @@ DECLARE_OPTIONS(Recorder, "P2PVR Recorder options")
("p2pvr.recorder.extension", Options::value(&extension, "mpg"),
"File extension to save with (default: avi)")
("p2pvr.recorder.muxercommand", Options::value(&muxerCommand, "/usr/bin/ffmpeg -f mpegts -i - -f dvd -codec copy -"),
- "File extension to save with (default: '/usr/bin/ffmpeg -f mpegts -i - -f dvd -codec copy -')")
+ "Command to perform TS->PS muxing (default: '/usr/bin/ffmpeg -f mpegts -i - -f dvd -codec copy -')")
END_OPTIONS(Recorder);
Recorder::Recorder(Ice::ObjectAdapterPtr a, IceUtil::TimerPtr t) :
diff --git a/p2pvr/daemon/storage.cpp b/p2pvr/daemon/storage.cpp
index 9d9345d..79280a1 100644
--- a/p2pvr/daemon/storage.cpp
+++ b/p2pvr/daemon/storage.cpp
@@ -273,3 +273,47 @@ Storage::DeleteFrom(const fs::path & path, const fs::path & from)
}
}
+void
+Storage::Send(const std::string & id, const P2PVR::RawDataClientPrx & target, Ice::Long start, Ice::Long len, const Ice::Current &)
+{
+ fs::path path = root / byAll / id;
+ path.replace_extension(FindExtension(id));
+ auto fd = open(path.string().c_str(), O_RDONLY | O_LARGEFILE);
+ if (fd < 0) {
+ Logger()->messagebf(LOG_ERR, "%s: Failed to open file for reading at %s (%d:%s)", __PRETTY_FUNCTION__,
+ path, errno, strerror(errno));
+ throw P2PVR::StorageException(path.string(), strerror(errno));
+ }
+ lseek(fd, start, SEEK_SET);
+ auto end = std::min<off_t>(fs::file_size(path), start + len);
+ while (start < end) {
+ P2PVR::Data buf(16 * 1024);
+ auto r = read(fd, &buf.front(), std::min<size_t>(buf.size(), end - start));
+ if (r < 0) {
+ Logger()->messagebf(LOG_ERR, "%s: Failed to read file %s (%d:%s)", __PRETTY_FUNCTION__,
+ path, errno, strerror(errno));
+ close(fd);
+ throw P2PVR::StorageException(path.string(), strerror(errno));
+ }
+ if (target->NewData(buf)) {
+ close(fd);
+ return;
+ }
+ start += r;
+ }
+ close(fd);
+}
+
+Ice::Long
+Storage::FileSize(const std::string & id, const Ice::Current &)
+{
+ fs::path path = root / byAll / id;
+ try {
+ path.replace_extension(FindExtension(id));
+ return fs::file_size(path);
+ }
+ catch (...) {
+ throw P2PVR::StorageException(path.string(), "Couldn't get file size");
+ }
+}
+
diff --git a/p2pvr/daemon/storage.h b/p2pvr/daemon/storage.h
index 093e00d..86bc76a 100644
--- a/p2pvr/daemon/storage.h
+++ b/p2pvr/daemon/storage.h
@@ -13,6 +13,8 @@ class Storage : public P2PVR::Storage {
P2PVR::RawDataClientPrx OpenForWrite(const std::string &, const Ice::Current &);
void Close(const P2PVR::RawDataClientPrx & file, const Ice::Current &);
void Delete(const std::string &, const Ice::Current &);
+ Ice::Long FileSize(const std::string &, const Ice::Current &);
+ void Send(const std::string &, const P2PVR::RawDataClientPrx & target, Ice::Long start, Ice::Long len, const Ice::Current &);
INITOPTIONS;
diff --git a/p2pvr/ice/p2pvr.ice b/p2pvr/ice/p2pvr.ice
index 08356ae..6b9d31b 100644
--- a/p2pvr/ice/p2pvr.ice
+++ b/p2pvr/ice/p2pvr.ice
@@ -66,6 +66,8 @@ module P2PVR {
idempotent RawDataClient * OpenForWrite(string guid) throws StorageException;
idempotent void Close(RawDataClient * file) throws StorageException;
idempotent void Delete(string guid) throws StorageException;
+ idempotent long FileSize(string guid) throws StorageException;
+ void Send(string guid, RawDataClient * target, long start, long len) throws StorageException;
};
interface Recordings {
diff --git a/p2pvr/lib/serviceStreamer.cpp b/p2pvr/lib/serviceStreamer.cpp
index f7a7dc2..b9de8fd 100644
--- a/p2pvr/lib/serviceStreamer.cpp
+++ b/p2pvr/lib/serviceStreamer.cpp
@@ -16,6 +16,18 @@ ServiceStreamer::ServiceStreamer(int sid, P2PVR::RawDataClientPrx t, const Ice::
{
}
+ServiceStreamer::ServiceStreamer(int sid, P2PVR::RawDataClientPrx t, const P2PVR::DevicesPrx & d, const P2PVR::SIPrx & s, const Ice::ObjectAdapterPtr & a) :
+ adapter(a),
+ devs(d),
+ si(s),
+ target(t),
+ patParser(adapter, new BindSiParserHandler<ProgramAssociationMapPtr, SiProgramAssociationParser>(boost::bind(&ServiceStreamer::HandlePAT, this, _1))),
+ pmtParser(adapter, new BindSiParserHandler<DVBSI::ProgramMapPtr, SiProgramMapParser>(boost::bind(&ServiceStreamer::HandlePMT, this, _1))),
+ serviceId(sid),
+ patHandle(0), pmtStream(0), pmtHandle(0), serviceHandle(0)
+{
+}
+
ServiceStreamer::~ServiceStreamer()
{
}
diff --git a/p2pvr/lib/serviceStreamer.h b/p2pvr/lib/serviceStreamer.h
index bdabe7a..20ea8e2 100644
--- a/p2pvr/lib/serviceStreamer.h
+++ b/p2pvr/lib/serviceStreamer.h
@@ -12,6 +12,7 @@
class ServiceStreamer {
public:
ServiceStreamer(int sid, P2PVR::RawDataClientPrx, const Ice::CommunicatorPtr & ic, const Ice::ObjectAdapterPtr & a);
+ ServiceStreamer(int sid, P2PVR::RawDataClientPrx, const P2PVR::DevicesPrx & d, const P2PVR::SIPrx & s, const Ice::ObjectAdapterPtr & a);
~ServiceStreamer();
bool HandlePAT(ProgramAssociationMapPtr pam);
diff --git a/p2pvr/p2comp/Jamfile.jam b/p2pvr/p2comp/Jamfile.jam
new file mode 100644
index 0000000..6a847b7
--- /dev/null
+++ b/p2pvr/p2comp/Jamfile.jam
@@ -0,0 +1,10 @@
+lib p2pvrp2comp :
+ [ glob-tree *.cpp ]
+ : :
+ <library>../ice//p2pvrice
+ <library>../lib//p2pvrlib
+ <library>..//p2streams
+ <library>..//p2ice
+ <library>..//p2cgi
+ <implicit-dependency>../ice//p2pvrice
+ ;
diff --git a/p2pvr/p2comp/recordingStream.cpp b/p2pvr/p2comp/recordingStream.cpp
new file mode 100644
index 0000000..21e8684
--- /dev/null
+++ b/p2pvr/p2comp/recordingStream.cpp
@@ -0,0 +1,80 @@
+#include <p2pvr.h>
+#include <options.h>
+#include <misc.h>
+#include <temporaryIceAdapterObject.h>
+#include "streamSinkWrapper.h"
+#include "streamBase.h"
+#include <limits>
+#include <cgiRequestContext.h>
+
+class RecordingStream : public StreamBase {
+ public:
+ RecordingStream(ScriptNodePtr p) :
+ StreamBase(p),
+ recording(p, "recording")
+ {
+ }
+
+ void runStream(const Sink & sink, ExecContext * ec) const
+ {
+ auto storage = ice->GetProxy<P2PVR::StoragePrx>("Storage", ec);
+ assert(storage);
+
+ std::promise<int> prom;
+ {
+ TemporarayIceAdapterObject<P2PVR::RawDataClient> output(adapter, new StreamSinkWrapper(sink, prom));
+
+ try {
+ auto range = getRange(ec);
+ if (!range) {
+ range = { std::string(), 0, (unsigned long long)(storage->FileSize(recording(ec)) - 1) };
+ }
+ storage->Send(recording(ec), output, range->Start, range->End - range->Start + 1);
+ }
+ catch (const std::ios_base::failure &) {
+ }
+ }
+ }
+
+ boost::optional<unsigned long long> getSizeInBytes(ExecContext * ec) const override
+ {
+ auto storage = ice->GetProxy<P2PVR::StoragePrx>("Storage", ec);
+ assert(storage);
+ return storage->FileSize(recording(ec));
+ }
+
+ bool isSeekable() const override
+ {
+ return true;
+ }
+
+ boost::optional<RangeResponse> getRange(ExecContext * ec) const override
+ {
+ auto crc = dynamic_cast<CgiRequestContext *>(ec);
+ if (crc) {
+ auto range = crc->getRequestRange();
+ if (range && range->Unit == "bytes") {
+ auto storage = ice->GetProxy<P2PVR::StoragePrx>("Storage", ec);
+ assert(storage);
+ auto size = storage->FileSize(recording(ec));
+ if (!range->Start) {
+ range->Start = 0;
+ }
+ if (!range->End) {
+ range->End = size - 1;
+ }
+ else {
+ range->End = std::min<RangePos>(*range->End, size - 1);
+ }
+ return (RangeResponse){ std::string("bytes"), *range->Start, *range->End };
+ }
+ }
+ return boost::optional<RangeResponse>();
+ }
+
+ private:
+ Variable recording;
+};
+
+DECLARE_LOADER("p2pvrrecordingstream", RecordingStream);
+
diff --git a/p2pvr/p2comp/serviceStream.cpp b/p2pvr/p2comp/serviceStream.cpp
new file mode 100644
index 0000000..aedb4ad
--- /dev/null
+++ b/p2pvr/p2comp/serviceStream.cpp
@@ -0,0 +1,58 @@
+#include <commonObjects.h>
+#include <iceDataSource.h>
+#include <serviceStreamer.h>
+#include <options.h>
+#include <misc.h>
+#include <muxer.h>
+#include <future>
+#include <temporaryIceAdapterObject.h>
+#include "streamSinkWrapper.h"
+#include "streamBase.h"
+
+class ServiceStream : public StreamBase {
+ public:
+ ServiceStream(ScriptNodePtr p) :
+ StreamBase(p),
+ serviceId(p, "serviceId")
+ {
+ }
+
+ void runStream(const Sink & sink, ExecContext * ec) const
+ {
+ auto devices = ice->GetProxy<P2PVR::DevicesPrx>("Devices", ec);
+ assert(devices);
+ auto si = ice->GetProxy<P2PVR::SIPrx>("SI", ec);
+ assert(si);
+
+ std::promise<int> prom;
+ {
+ TemporarayIceAdapterObject<P2PVR::RawDataClient> output(adapter, new StreamSinkWrapper(sink, prom));
+ {
+ TemporarayIceAdapterObject<P2PVR::RawDataClient> muxer(adapter, new Muxer(output, muxerCommand));
+
+ ServiceStreamerPtr ss(new ServiceStreamer(serviceId(ec), muxer, devices, si, adapter));
+
+ ss->Start();
+ prom.get_future().get();
+ ss->Stop();
+ }
+ }
+ }
+
+ INITOPTIONS;
+
+ private:
+ Variable serviceId;
+
+ static std::string muxerCommand;
+};
+
+DECLARE_OPTIONS(ServiceStream, "P2PVR Live Stream options")
+("p2pvr.livestream.muxercommand", Options::value(&muxerCommand, "/usr/bin/ffmpeg -f mpegts -i - -f dvd -codec copy -"),
+ "Command to perform TS->PS muxing (default: '/usr/bin/ffmpeg -f mpegts -i - -f dvd -codec copy -')")
+END_OPTIONS(ServiceStream);
+
+std::string ServiceStream::muxerCommand;
+
+DECLARE_LOADER("p2pvrservicestream", ServiceStream);
+
diff --git a/p2pvr/p2comp/streamBase.cpp b/p2pvr/p2comp/streamBase.cpp
new file mode 100644
index 0000000..2ed4954
--- /dev/null
+++ b/p2pvr/p2comp/streamBase.cpp
@@ -0,0 +1,50 @@
+#include "streamBase.h"
+#include <logger.h>
+#include <Ice/ObjectAdapter.h>
+#include <commonObjects.h>
+#include <exception>
+
+StreamBase::StreamBase(ScriptNodePtr p) :
+ Stream(p),
+ dataSource(p, "datasource")
+{
+}
+
+StreamBase::~StreamBase()
+{
+ adapter->deactivate();
+ adapter->destroy();
+}
+
+void
+StreamBase::loadComplete(const CommonObjects * co)
+{
+ ice = co->dataSource<IceDataSource>(dataSource(NULL));
+
+ Ice::CommunicatorPtr ic(ice->GetCommunicator());
+ for (int port = startPort; !adapter && port <= endPort; ++port) {
+ try {
+ adapter = ic->createObjectAdapterWithEndpoints(std::string(), stringbf("default -p %d", port));
+ adapter->activate();
+ Logger()->messagebf(LOG_INFO, "%s: adapter activated on port %d", __PRETTY_FUNCTION__, port);
+ }
+ catch (...) {
+ adapter = NULL;
+ }
+ }
+ if (!adapter) {
+ Logger()->messagebf(LOG_ERR, "%s: Failed to bind to a free port in the configured range (%d - %d)",
+ __PRETTY_FUNCTION__, startPort, endPort);
+ throw std::runtime_error("Failed to bind to a free port");
+ }
+}
+
+DECLARE_OPTIONS(StreamBase, "P2PVR Stream Client options")
+("p2pvr.stream.startport", Options::value(&startPort, 10001),
+ "Start of port range for ICE adapter when receiving streams")
+("p2pvr.stream.endport", Options::value(&endPort, 11000),
+ "End of port range for ICE adapter when receiving streams")
+END_OPTIONS(StreamBase);
+
+int StreamBase::startPort;
+int StreamBase::endPort;
diff --git a/p2pvr/p2comp/streamBase.h b/p2pvr/p2comp/streamBase.h
new file mode 100644
index 0000000..279625b
--- /dev/null
+++ b/p2pvr/p2comp/streamBase.h
@@ -0,0 +1,26 @@
+#ifndef STREAMBASE_H
+#define STREAMBASE_H
+
+#include <iceDataSource.h>
+#include <stream.h>
+#include <options.h>
+
+class StreamBase : public Stream {
+ public:
+ StreamBase(ScriptNodePtr p);
+ ~StreamBase();
+
+ void loadComplete(const CommonObjects * co) override;
+
+ INITOPTIONS;
+ protected:
+ Variable dataSource;
+ const IceDataSource * ice;
+ Ice::ObjectAdapterPtr adapter;
+
+ static int startPort;
+ static int endPort;
+};
+
+#endif
+
diff --git a/p2pvr/p2comp/streamSinkWrapper.cpp b/p2pvr/p2comp/streamSinkWrapper.cpp
new file mode 100644
index 0000000..3dd3d2b
--- /dev/null
+++ b/p2pvr/p2comp/streamSinkWrapper.cpp
@@ -0,0 +1,28 @@
+#include "streamSinkWrapper.h"
+
+#define LOCK(lock) std::lock_guard<std::mutex> _lock##__LINE_NO__##lock(lock);
+
+StreamSinkWrapper::StreamSinkWrapper(const Stream::Sink & s, std::promise<int> & p) :
+ sink(s),
+ promise(p),
+ completed(false)
+{
+}
+
+bool
+StreamSinkWrapper::NewData(const P2PVR::Data & data, const Ice::Current &)
+{
+ try {
+ sink(reinterpret_cast<const char *>(&data.front()), data.size());
+ return false;
+ }
+ catch (const std::ios_base::failure &) {
+ LOCK(lock) {
+ if (completed) return true;
+ completed = true;
+ promise.set_value(0);
+ return true;
+ }
+ }
+}
+
diff --git a/p2pvr/p2comp/streamSinkWrapper.h b/p2pvr/p2comp/streamSinkWrapper.h
new file mode 100644
index 0000000..0cbf915
--- /dev/null
+++ b/p2pvr/p2comp/streamSinkWrapper.h
@@ -0,0 +1,23 @@
+#ifndef STREAMSINKWRAPPER
+#define STREAMSINKWRAPPER
+
+#include <stream.h>
+#include <dvb.h>
+#include <future>
+#include <mutex>
+
+class StreamSinkWrapper : public P2PVR::RawDataClient {
+ public:
+ StreamSinkWrapper(const Stream::Sink & s, std::promise<int> & p);
+
+ bool NewData(const P2PVR::Data & data, const Ice::Current &) override;
+
+ private:
+ const Stream::Sink & sink;
+ std::promise<int> & promise;
+ bool completed;
+ std::mutex lock;
+};
+
+#endif
+
diff --git a/p2pvr/streamer/Jamfile.jam b/p2pvr/streamer/Jamfile.jam
deleted file mode 100644
index d17e9b2..0000000
--- a/p2pvr/streamer/Jamfile.jam
+++ /dev/null
@@ -1,7 +0,0 @@
-lib streamer :
- [ glob-tree *.cpp ]
- : :
- <library>../ice//p2pvrice
- <library>../lib//p2pvrlib
- <implicit-dependency>../ice//p2pvrice
- ;
diff --git a/p2pvr/streamer/streamer.cpp b/p2pvr/streamer/streamer.cpp
deleted file mode 100644
index 2722266..0000000
--- a/p2pvr/streamer/streamer.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#include <daemonBase.h>
-#include <p2pvr.h>
-#include "globalDevices.h"
-#include "si.h"
-#include <serviceStreamer.h>
-#include <fileSink.h>
-#include <muxer.h>
-
-class P2PvrStreamer : public DaemonBase {
- public:
- P2PvrStreamer(int argc, char ** argv) :
- DaemonBase(argc, argv)
- {
- }
-
- void addServants(const Ice::ObjectAdapterPtr & adapter, const IceUtil::TimerPtr &) const
- {
- adapter->add(new GlobalDevices(), ic->stringToIdentity("GlobalDevices"));
- adapter->add(new SI(), ic->stringToIdentity("SI"));
- auto output = P2PVR::RawDataClientPrx::checkedCast(adapter->addWithUUID(new FileSink(1)));
- assert(output);
- auto muxer = P2PVR::RawDataClientPrx::checkedCast(adapter->addWithUUID(new Muxer(output, "/usr/bin/ffmpeg -f mpegts -i - -f dvd -codec copy -")));
- assert(muxer);
- ss = ServiceStreamerPtr(new ServiceStreamer(4287, muxer, ic, adapter));
- assert(ss);
- }
-
- void run() const
- {
- IceUtil::TimerPtr timer = new IceUtil::Timer();
- Logger()->messagebf(LOG_INFO, "Creating adapter (%s, %s)", Adapter, Endpoint);
- auto adapter = ic->createObjectAdapterWithEndpoints(Adapter, Endpoint);
- addServants(adapter, timer);
- adapter->activate();
-
- ss->Start();
-
- ic->waitForShutdown();
- timer->destroy();
- }
-
- void shutdown() const
- {
- ss->Stop();
- DaemonBase::shutdown();
- }
- private:
- mutable ServiceStreamerPtr ss;
-};
-
-DECLARE_GENERIC_LOADER("p2pvrstreamer", DaemonLoader, P2PvrStreamer);
-
diff --git a/p2pvr/webfe/.htaccess b/p2pvr/webfe/.htaccess
new file mode 100644
index 0000000..1588083
--- /dev/null
+++ b/p2pvr/webfe/.htaccess
@@ -0,0 +1,12 @@
+<Files "p2fcgi">
+ sethandler fcgid-script
+</Files>
+
+RewriteEngine on
+RewriteCond %{REQUEST_URI} !^/css/
+RewriteCond %{REQUEST_URI} !^/js/
+RewriteCond %{REQUEST_URI} !^/img/
+RewriteCond %{REQUEST_URI} !^/[^/]+\.[^/]+$
+RewriteCond %{REQUEST_URI} !^/p2fcgi
+RewriteRule ^(.*) /p2fcgi/\1 [L]
+
diff --git a/p2pvr/webfe/.p2config b/p2pvr/webfe/.p2config
index f5ea06f..db4692d 100644
--- a/p2pvr/webfe/.p2config
+++ b/p2pvr/webfe/.p2config
@@ -1,5 +1,6 @@
ice.client.sliceclient = ice/common.ice
library = libp2pvrice.so
+library = libp2pvrp2comp.so
ice.client.sliceclient = ice/dvbsi.ice
ice.client.sliceclient = ice/dvb.ice
ice.client.sliceclient = ice/p2pvr.ice
diff --git a/p2pvr/webfe/p2fcgi b/p2pvr/webfe/p2fcgi
new file mode 120000
index 0000000..b0e5628
--- /dev/null
+++ b/p2pvr/webfe/p2fcgi
@@ -0,0 +1 @@
+/usr/bin/p2fcgi \ No newline at end of file
diff --git a/p2pvr/webfe/present/stream/recording.xml b/p2pvr/webfe/present/stream/recording.xml
new file mode 100644
index 0000000..fc7a1b0
--- /dev/null
+++ b/p2pvr/webfe/present/stream/recording.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<view xmlns:project2="http://project2.randomdan.homeip.net">
+ <project2:stream contenttype="video/mpeg">
+ <project2:p2pvrrecordingstream datasource="p2pvr">
+ <recording source="uri" index="2" />
+ </project2:p2pvrrecordingstream>
+ </project2:stream>
+</view>
diff --git a/p2pvr/webfe/present/stream/service.xml b/p2pvr/webfe/present/stream/service.xml
new file mode 100644
index 0000000..8587636
--- /dev/null
+++ b/p2pvr/webfe/present/stream/service.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<view xmlns:project2="http://project2.randomdan.homeip.net">
+ <project2:stream contenttype="video/mpeg">
+ <project2:p2pvrservicestream datasource="p2pvr">
+ <serviceId source="uri" index="2" />
+ </project2:p2pvrservicestream>
+ </project2:stream>
+</view>