Google
  Web www.spinics.net

Re: LiveBuffer for vdr 1.7.x

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]


On 23/12/2011 12:11, Dominic Evans wrote:
It's a patch from yaVDR and it doesn't apply to the vdr source tree as is
(at least for me).
Here's the patch from yaVDR rebased to apply to vanilla 1.7.21 sources:

https://gist.github.com/1513894

https://raw.github.com/gist/1513894

_______________________________________________
vdr mailing list
vdr@xxxxxxxxxxx
http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr
It works, thanks !

There is a typo error in vdr.c but that's all.

For gentoo users, I attached the patches I use. I relocated some hunk of config.c and config.h and renamed USE_LIVEBUFFER to LIVEBUFFER in Makefile.

Patch the ebuild and put the other patch in /etc/portage/patches/media-video/vdr-1.7.21 (I use the epatch_user tool instead of the script, see the ebuild patch).

Marc.
>From d99a28abfd108690028d7847b8736e542b089fd0 Mon Sep 17 00:00:00 2001
From: Dominic Evans <oldmanuk@xxxxxxxxx>
Date: Fri, 23 Dec 2011 10:51:30 +0000
Subject: [PATCH] opt-96-livebuffer12-rmm.dpatch rebased onto 1.7.21

---
 Makefile     |    5 +
 config.c     |    7 +
 config.h     |    4 +
 device.c     |   21 +++
 device.h     |    4 +
 dvbplayer.c  |   51 ++++++++
 livebuffer.c |  403 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 livebuffer.h |   47 +++++++
 menu.c       |  248 +++++++++++++++++++++++++++++++++++-
 menu.h       |   22 +++
 osdbase.h    |    3 +
 player.c     |   11 ++
 po/de_DE.po  |    3 +
 recorder.c   |   10 ++
 recorder.h   |   15 ++
 recording.h  |   19 +++
 timers.c     |    8 +
 timers.h     |    4 +
 vdr.c        |   52 ++++++++-
 videodir.c   |   18 +++
 videodir.h   |    3 +
 21 files changed, 951 insertions(+), 7 deletions(-)
 create mode 100644 livebuffer.c
 create mode 100644 livebuffer.h

diff --git a/Makefile b/Makefile
index 18d7eb9..bc49dac 100644
--- a/Makefile
+++ b/Makefile
@@ -60,6 +60,11 @@ DEFINES += -DBIDI
 LIBS += $(shell pkg-config --libs fribidi)
 endif
 
+ifdef LIVEBUFFER
+DEFINES += -DUSE_LIVEBUFFER
+OBJS += livebuffer.o
+endif
+
 LIRC_DEVICE ?= /dev/lircd
 RCU_DEVICE  ?= /dev/ttyS1
 
diff --git a/config.c b/config.c
index 73bb00d..14146a0 100644
--- a/config.c
+++ b/config.c
@@ -397,6 +397,10 @@
   CurrentDolby = 0;
   InitialChannel = "";
   InitialVolume = -1;
+#ifdef USE_LIVEBUFFER
+  LiveBufferSize = 30;
+  LiveBufferMaxFileSize = 100;
+#endif /*USE_LIVEBUFFER*/
   ChannelsWrap = 0;
   EmergencyExit = 1;
 }
@@ -589,6 +589,10 @@ bool cSetup::Parse(const char *Name, const char *Value)
   else if (!strcasecmp(Name, "CurrentDolby"))        CurrentDolby       = atoi(Value);
   else if (!strcasecmp(Name, "InitialChannel"))      InitialChannel     = Value;
   else if (!strcasecmp(Name, "InitialVolume"))       InitialVolume      = atoi(Value);
+#ifdef USE_LIVEBUFFER
+  else if (!strcasecmp(Name, "LiveBufferSize"))        LiveBufferSize        = atoi(Value);
+  else if (!strcasecmp(Name, "LiveBufferMaxFileSize")) LiveBufferMaxFileSize = atoi(Value);
+#endif /*USE_LIVEBUFFER*/
   else if (!strcasecmp(Name, "ChannelsWrap"))        ChannelsWrap       = atoi(Value);
   else if (!strcasecmp(Name, "EmergencyExit"))       EmergencyExit      = atoi(Value);
   else
@@ -685,6 +689,9 @@
   Store("CurrentDolby",       CurrentDolby);
   Store("InitialChannel",     InitialChannel);
   Store("InitialVolume",      InitialVolume);
+#ifdef USE_LIVEBUFFER
+  Store("LiveBufferSize",     LiveBufferSize);
+#endif  /* LIVEBUFFER */
   Store("ChannelsWrap",       ChannelsWrap);
   Store("EmergencyExit",      EmergencyExit);
 
diff --git a/config.h b/config.h
index c51e3df..1972195 100644
--- a/config.h
+++ b/config.h
@@ -288,6 +288,10 @@
   int CurrentVolume;
   int CurrentDolby;
   int InitialVolume;
+#ifdef USE_LIVEBUFFER
+  int LiveBufferSize;
+  int LiveBufferMaxFileSize;
+#endif /*USE_LIVEBUFFER*/
   int ChannelsWrap;
   int EmergencyExit;
   int __EndData__;
diff --git a/device.c b/device.c
index ba098d8..172f3b3 100644
--- a/device.c
+++ b/device.c
@@ -18,6 +18,10 @@
 #include "receiver.h"
 #include "status.h"
 #include "transfer.h"
+#ifdef USE_LIVEBUFFER
+#include "menu.h"
+#include "interface.h"
+#endif /*USE_LIVEBUFFER*/
 
 // --- cLiveSubtitle ---------------------------------------------------------
 
@@ -661,6 +665,14 @@ bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView)
                               return false;
         case scrNoTransfer:   Skins.Message(mtError, tr("Can't start Transfer Mode!"));
                               return false;
+#ifdef USE_LIVEBUFFER
+        case srcStillWritingLiveBuffer:
+           if(Interface->Confirm(tr("Still writing timeshift data to recording. Abort?")))
+              cRecordControls::CancelWritingBuffer();
+           else
+              if(cRecordControls::IsWritingBuffer()) return false;
+           break;
+#endif /*USE_LIVEBUFFER*/
         case scrFailed:       break; // loop will retry
         default:              esyslog("ERROR: invalid return value from SetChannel");
         }
@@ -718,8 +730,17 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
 
   if (NeedsTransferMode) {
      if (Device && CanReplay()) {
+#ifdef USE_LIVEBUFFER
+        if(LiveView && !cRecordControls::CanSetLiveChannel(Channel))
+           return cRecordControls::IsWritingBuffer() ? srcStillWritingLiveBuffer : scrFailed;
+#endif /*USE_LIVEBUFFER*/
         cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel
         if (Device->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()!
+#ifdef USE_LIVEBUFFER
+           if(LiveView)
+              cRecordControls::SetLiveChannel(Device, Channel);
+           else
+#endif /*USE_LIVEBUFFER*/
            cControl::Launch(new cTransferControl(Device, Channel));
         else
            Result = scrNoTransfer;
diff --git a/device.h b/device.h
index fd587a8..2bebe89 100644
--- a/device.h
+++ b/device.h
@@ -31,7 +31,11 @@
 #define MAXVOLUME         255
 #define VOLUMEDELTA         5 // used to increase/decrease the volume
 
+#ifdef USE_LIVEBUFFER
+enum eSetChannelResult { scrOk, scrNotAvailable, scrNoTransfer, scrFailed, srcStillWritingLiveBuffer };
+#else
 enum eSetChannelResult { scrOk, scrNotAvailable, scrNoTransfer, scrFailed };
+#endif /*USE_LIVEBUFFER*/
 
 enum ePlayMode { pmNone,           // audio/video from decoder
                  pmAudioVideo,     // audio/video from player
diff --git a/dvbplayer.c b/dvbplayer.c
index 017df6d..800a31d 100644
--- a/dvbplayer.c
+++ b/dvbplayer.c
@@ -15,6 +15,9 @@
 #include "ringbuffer.h"
 #include "thread.h"
 #include "tools.h"
+#ifdef USE_LIVEBUFFER
+#include "menu.h"
+#endif /*USE_LIVEBUFFER*/
 
 // --- cPtsIndex -------------------------------------------------------------
 
@@ -35,6 +38,9 @@ public:
   void Clear(void);
   void Put(uint32_t Pts, int Index);
   int FindIndex(uint32_t Pts);
+#ifdef USE_LIVEBUFFER
+  void SetIndex(int Index) {lastFound = Index;};
+#endif /*USE_LIVEBUFFER*/
   };
 
 cPtsIndex::cPtsIndex(void)
@@ -205,7 +211,12 @@ private:
   cRingBufferFrame *ringBuffer;
   cPtsIndex ptsIndex;
   cFileName *fileName;
+#ifdef USE_LIVEBUFFER
+  cIndex *index;
+  cIndexFile *indexFile;
+#else
   cIndexFile *index;
+#endif /*USE_LIVEBUFFER*/
   cUnbufferedFile *replayFile;
   double framesPerSecond;
   bool isPesRecording;
@@ -270,18 +281,35 @@ cDvbPlayer::cDvbPlayer(const char *FileName)
   dropFrame = NULL;
   isyslog("replay %s", FileName);
   fileName = new cFileName(FileName, false, false, isPesRecording);
+#ifndef USE_LIVEBUFFER
   replayFile = fileName->Open();
   if (!replayFile)
      return;
+#endif /*USE_LIVEBUFFER*/
   ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE);
   // Create the index file:
+#ifdef USE_LIVEBUFFER
+  indexFile = NULL;
+  index = cRecordControls::GetLiveIndex(FileName);
+  if(!index)
+     index = indexFile = new cIndexFile(FileName, false, isPesRecording);
+#else
   index = new cIndexFile(FileName, false, isPesRecording);
+#endif /*USE_LIVEBUFFER*/
   if (!index)
      esyslog("ERROR: can't allocate index");
   else if (!index->Ok()) {
      delete index;
      index = NULL;
      }
+#ifdef USE_LIVEBUFFER
+  readIndex = Resume();
+  if (readIndex >= 0) {
+     ptsIndex.SetIndex(readIndex);
+     isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
+  } else
+     replayFile = fileName->Open();
+#endif /*USE_LIVEBUFFER*/
 }
 
 cDvbPlayer::~cDvbPlayer()
@@ -289,7 +317,11 @@ cDvbPlayer::~cDvbPlayer()
   Save();
   Detach();
   delete readFrame; // might not have been stored in the buffer in Action()
+#ifdef USE_LIVEBUFFER
+  delete indexFile;
+#else
   delete index;
+#endif /*USE_LIVEBUFFER*/
   delete fileName;
   delete ringBuffer;
 }
@@ -387,9 +419,11 @@ void cDvbPlayer::Action(void)
   uchar *p = NULL;
   int pc = 0;
 
+#ifndef USE_LIVEBUFFER
   readIndex = Resume();
   if (readIndex >= 0)
      isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
+#endif /*USE_LIVEBUFFER*/
 
   nonBlockingFileReader = new cNonBlockingFileReader;
   int Length = 0;
@@ -436,6 +470,10 @@ void cDvbPlayer::Action(void)
                          if (NewIndex <= 0 && readIndex > 0)
                             NewIndex = 1; // make sure the very first frame is delivered
                          NewIndex = index->GetNextIFrame(NewIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length, TimeShiftMode);
+#ifdef USE_LIVEBUFFER
+                         if (NewIndex < 0 && TimeShiftMode) // Why should we wait for a timeout if not pdForward
+                            SwitchToPlayFrame = Index;
+#endif
                          if (NewIndex < 0 && TimeShiftMode && playDir == pdForward)
                             SwitchToPlayFrame = Index;
                          Index = NewIndex;
@@ -454,6 +492,15 @@ void cDvbPlayer::Action(void)
                       off_t FileOffset;
                       if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset))
                          readIndex++;
+#ifdef USE_LIVEBUFFER
+                      else if(index && index->First() && (readIndex < index->First())) {
+                         int old = readIndex;
+                         readIndex = index->GetNextIFrame(index->First()+1, true, NULL, NULL, NULL, true);
+                         isyslog("Jump before start of livebuffer cortrected %d->%d First %d", old, readIndex, index->First());
+                         if(readIndex <= index->First())
+                            eof = true;
+                      }
+#endif /*USE_LIVEBUFFER*/
                       else
                          eof = true;
                       }
@@ -587,7 +634,11 @@ void cDvbPlayer::Action(void)
              else if (Index <= 0 || SwitchToPlayFrame && Index >= SwitchToPlayFrame)
                 SwitchToPlay = true;
              if (SwitchToPlay) {
+#ifdef USE_LIVEBUFFER
+                if (!SwitchToPlayFrame || (playDir == pdBackward))
+#else
                 if (!SwitchToPlayFrame)
+#endif /*USE_LIVEBUFFER*/
                    Empty();
                 DevicePlay();
                 playMode = pmPlay;
diff --git a/livebuffer.c b/livebuffer.c
new file mode 100644
index 0000000..afc988d
--- /dev/null
+++ b/livebuffer.c
@@ -0,0 +1,403 @@
+#ifdef USE_LIVEBUFFER
+#include "livebuffer.h"
+#if VDRVERSNUM >= 10716
+
+#include <vector>
+#include "videodir.h"
+#include "recording.h"
+#include "skins.h"
+#include "player.h"
+
+#define WAIT_WRITING_COUNT 1000
+#define WAIT_WRITING_SLEEP 10000
+
+#define WAIT_TERMINATE_COUNT 300
+#define WAIT_TERMINATE_SLEEP 10000
+
+struct tLiveIndex {
+  int index;
+  uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
+  int reserved:7;     // reserved for future use
+  int independent:1;  // marks frames that can be displayed by themselves (for trick modes)
+  uint16_t number:16; // up to 64K files per recording
+  tLiveIndex(int Index, bool Independent, uint16_t Number, off_t Offset)
+  {
+    index = Index;
+    offset = Offset;
+    reserved = 0;
+    independent = Independent;
+    number = Number;
+  }
+}; // tLiveIndex
+
+class cLiveIndex : public cIndex {
+public:
+	cLiveIndex(const char *FileName): bufferFileName(FileName, false), bufferBaseName(FileName) {
+		resumePos = -1;
+		lastPos = lastGet = lastBuf = 0;
+		lastFileNumber=1;
+		dropFile = false;
+		maxSize = Setup.LiveBufferSize * 60 * DEFAULTFRAMESPERSECOND;
+		idx.reserve(maxSize+1);
+	}; // cLiveIndex
+	virtual ~cLiveIndex() {
+	}; // ~cLiveIndex
+	virtual bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset) {
+		cMutexLock lock(&idx_lock);
+		idx.push_back(tLiveIndex(++lastPos, Independent, FileNumber, FileOffset));
+		while(((idx.size() > maxSize) && (lastGet ? (lastGet > First()) : true) && (lastBuf ? (lastBuf > First()) : true)) || dropFile) {
+			if(idx.front().number != lastFileNumber) {
+				isyslog("Deleting old livebuffer file #%d (%d)", lastFileNumber, dropFile);
+				system(cString::sprintf("ls -l %s/%05d.ts | grep -- '->' | sed -e's/.*-> //' | xargs rm -rf", (const char *)bufferBaseName, lastFileNumber));  // for symlink video.xx
+				unlink(cString::sprintf("%s/%05d.ts", (const char *)bufferBaseName, lastFileNumber));
+				lastFileNumber = idx.front().number;
+				dropFile=false;
+			} // if
+			idx.erase(idx.begin());
+		} // if
+		return true;
+	}; // Write
+	virtual bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL) {
+		cMutexLock lock(&idx_lock);
+		std::vector<tLiveIndex>::iterator item = GetIndex(Index);
+		if(item == idx.end()) return false;
+		*FileNumber = item->number;
+		*FileOffset = item->offset;
+		if (Independent)
+			*Independent = item->independent;
+		item++;
+		if(item == idx.end()) return false;
+		if (Length) {
+			uint16_t fn = item->number;
+			off_t fo = item->offset;
+			if (fn == *FileNumber)
+				*Length = int(fo - *FileOffset);
+			else
+				*Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
+		} // if
+		lastGet = Index;
+		return true;
+	}; // Get
+	virtual int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false) {
+		cMutexLock lock(&idx_lock);
+		std::vector<tLiveIndex>::iterator item = GetIndex(Index);
+		if(item == idx.end()) {
+			if(Index < First() && Forward)
+				item = idx.begin();
+			else
+				return -1;
+		}
+		if(Forward) {
+			do {
+				item++;
+				if(item == idx.end()) return -1;
+			} while(!item->independent);
+		} else {
+			do {
+				if(item == idx.begin()) return -1;
+				item--;
+			} while(!item->independent);
+		} // if
+		uint16_t fn;
+		if (!FileNumber)
+			FileNumber = &fn;
+		off_t fo;
+		if (!FileOffset)
+			FileOffset = &fo;
+		*FileNumber = item->number;
+		*FileOffset = item->offset;
+		item++;
+		if(item == idx.end()) return -1;
+		if (Length) {
+			// all recordings end with a non-independent frame, so the following should be safe:
+			uint16_t fn = item->number;
+			off_t fo = item->offset;
+			if (fn == *FileNumber) {
+				*Length = int(fo - *FileOffset);
+			} else {
+				esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber);
+				*Length = -1;
+			} // if
+		} // if
+		return Index;
+	}; // GetNextIFrame
+	virtual bool SetBufferStart(int Frames) {
+		cMutexLock lock(&idx_lock);
+		abortBuf = false;
+		if(Frames <= 0) {
+			lastBuf = 0;
+			return false;
+		} // if
+		lastBuf = Last()-Frames;
+		if(lastBuf < First())
+			lastBuf = First();
+		lastBuf = GetNextIFrame(lastBuf, true);
+		return true;
+	} // SetBufferStart
+	virtual cUnbufferedFile *GetNextBuffer(int &Length, bool &Independent) {
+		if(abortBuf || !lastBuf) return NULL;
+		cMutexLock lock(&idx_lock);
+		std::vector<tLiveIndex>::iterator buff = GetIndex(lastBuf);
+		if((buff == idx.end()) || ((buff+1) == idx.end())) return NULL;
+		off_t offset = buff->offset;
+		int number   = buff->number;
+		cUnbufferedFile *ret = bufferFileName.SetOffset(number, offset);
+		Independent = buff->independent;
+		buff++;
+		lastBuf = buff->index;
+		if(number != buff->number)
+			Length = -1;
+		else
+			Length = buff->offset-offset;
+		return ret;
+	} // GetNextBuffer
+	virtual int Get(uint16_t FileNumber, off_t FileOffset) {
+		for ( std::vector<tLiveIndex>::iterator item = idx.begin(); item != idx.end(); item++)
+			if (item->number > FileNumber || ((item->number == FileNumber) && off_t(item->offset) >= FileOffset))
+				return item->index;
+		return lastPos;
+	}; // Get
+	virtual bool Ok(void)                    {return true;};
+	virtual int  First(void)                 {return idx.size() ? idx.front().index : -1;};
+	virtual int  Last(void)                  {return idx.size() ? idx.back().index  : -1;};
+	virtual void SetResume(int Index)        {resumePos = lastGet = Index;};
+	virtual int  GetResume(void)             {return resumePos;};
+	virtual bool StoreResume(int Index)      {resumePos=Index; lastGet=0; return true;};
+	virtual bool IsStillRecording(void)      {return true;};
+	virtual void Delete(void)                {};
+	virtual void DropFile(void)              {dropFile=true;};
+	virtual bool IsWritingBuffer(void)       {return lastBuf != 0;};
+	virtual void CancelWritingBuffer(void)   {abortBuf = true;};
+	virtual bool WritingBufferCanceled(void) {return abortBuf;};
+protected:
+	int firstPos;
+	int lastPos;
+	int resumePos;
+	int lastFileNumber;
+	int lastGet;
+	int lastBuf;
+	bool abortBuf;
+	bool dropFile;
+	unsigned int maxSize;
+	cFileName bufferFileName;
+	cString bufferBaseName;
+	cMutex idx_lock;
+	std::vector<tLiveIndex> idx;
+	virtual std::vector<tLiveIndex>::iterator GetIndex(int Index) {
+		if(!idx.size()) return idx.end();
+		std::vector<tLiveIndex>::iterator item = idx.begin();
+
+		unsigned int guess = Index-First(); // Try to guess the position
+		if(guess > 0) {
+			if(guess < idx.size())
+				item += guess;
+			else
+				item = idx.end()-1;
+		} // if
+		while(item->index < Index) {
+			item++;
+			if(item == idx.end())
+				return idx.end();
+		} // while
+		while(item->index > Index) {
+			if(item == idx.begin())
+				return idx.end();
+			item--;
+		} // while
+		if(item->index != Index)
+			return idx.end();
+		return item;
+	}; // GetIndex
+}; // cLiveIndex
+
+/*****************************************************************************/
+
+cString cLiveRecorder::liveFileName;
+
+cLiveRecorder::cLiveRecorder(const cChannel *Channel):cRecorder(FileName(), Channel, -1)
+              ,broken(false) {
+	handleError = false;
+	if(index) delete index;
+	index = new cLiveIndex(FileName());
+	Activate(true);
+}; // cLiveRecorder::cLiveRecorder
+
+cLiveRecorder::~cLiveRecorder() {
+	int maxWait = WAIT_TERMINATE_COUNT;
+	CancelWritingBuffer();
+	while(IsWritingBuffer() && maxWait--)
+		usleep(WAIT_TERMINATE_SLEEP);
+	Activate(false);
+	Cleanup();
+}; // cLiveRecorder::~cLiveRecorder
+
+bool cLiveRecorder::IsWritingBuffer() {
+	return index && ((cLiveIndex *)index)->IsWritingBuffer();
+} // cLiveRecorder::IsWritingBuffer
+
+void cLiveRecorder::CancelWritingBuffer() {
+	if(index) ((cLiveIndex *)index)->CancelWritingBuffer();
+} // cLiveRecorder::CancelWritingBuffer
+
+bool cLiveRecorder::NextFile(void) {
+	if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame
+		if(RunningLowOnDiskSpace() && index)
+			((cLiveIndex *)index)->DropFile();
+		if (fileSize > MEGABYTE(off_t(Setup.LiveBufferMaxFileSize)) || RunningLowOnDiskSpace()) {
+			recordFile = fileName->NextFile();
+			fileSize = 0;
+		} // if
+	} // if
+	return recordFile != NULL;
+} // cLiveRecorder::NextFile
+
+int cLiveRecorder::LastIFrame() {
+	if(!index) return 0;
+	int ret = index->GetNextIFrame(index->Last()-1, false);
+	return (ret > 0) ? ret : 0;
+}; // cLiveRecorder::LastIFrame
+
+int cLiveRecorder::LastFrame() { 
+	return index ? index->Last() : 0;
+}; // cLiveRecorder::LastFrame
+
+void cLiveRecorder::SetResume(int Index) { 
+	if(index) ((cLiveIndex *)index)->SetResume(Index);
+}; // cLiveRecorder::SetResume
+
+bool cLiveRecorder::SetBufferStart(time_t Start) {
+	if(!index) return false;
+	if(time(NULL) <= Start) return false;
+	int Frames = SecondsToFrames(time(NULL)-Start, frameDetector ? frameDetector->FramesPerSecond() : DEFAULTFRAMESPERSECOND); //test stop livebuffer 
+	return ((cLiveIndex *)index)->SetBufferStart(Frames);
+} // cLiveRecorder::SetBufferStart
+
+cIndex *cLiveRecorder::GetIndex() { 
+	return index;
+}; // cLiveRecorder::GetIndex
+
+bool cLiveRecorder::Cleanup() {
+	if(FileName()) 
+                if(-1 == system(cString::sprintf("ls -l %s/* | grep -- '->' | sed -e's/.*-> //' | xargs rm -rf", FileName()))) // for symlink video.xx
+                        return false;
+        else 
+		if(-1 == system(cString::sprintf("rm -rf %s/*", FileName())))
+			return false;
+	return true;
+}; // cLiveRecorder::Cleanup
+
+bool cLiveRecorder::Prepare() {
+	if (!MakeDirs(FileName(), true)) return false;
+	return Cleanup();
+}; // cLiveRecorder::Prepare
+
+const char *cLiveRecorder::FileName() {
+	if(!(const char *)liveFileName && BufferDirectory)
+		liveFileName = cString::sprintf("%s/LiveBuffer", BufferDirectory);
+	return liveFileName;
+}; // cLiveRecorder::FileName
+
+void cLiveRecorder::Activate(bool On) {
+	cRecorder::Activate(On);
+	if(!On) broken=true;
+} // cLiveRecorder::Activate
+
+void cLiveRecorder::Receive(uchar *Data, int Length) {
+	if(broken) {
+		isyslog("Continue live recorder on broken stream (maybe due to switching to same channel on other device)");
+		TsSetTeiOnBrokenPackets(Data, Length);
+		broken = false;
+	} // if
+	cRecorder::Receive(Data, Length);
+} // cLiveRecorder::Receive
+
+/*****************************************************************************/
+
+cBufferRecorder::cBufferRecorder(const char *FileName, const cChannel *Channel, int Priority, cIndex *LiveBufferIndex)
+                :cRecorder(FileName, Channel, Priority)
+                ,liveBufferIndex(LiveBufferIndex)
+                ,dropData(false) {
+	if(liveBufferIndex) dropData=true; // Drop new data till we have written most of the live buffer data
+} // cBufferRecorder::cBufferRecorder
+
+cBufferRecorder::~cBufferRecorder() {
+	if(liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0);
+} // cBufferRecorder::~cBufferRecorder
+
+void cBufferRecorder::Action(void) {
+	if(liveBufferIndex)
+		FillInitialData(NULL, 0);
+	dropData=false;
+	cRecorder::Action();
+	if(liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0);
+	liveBufferIndex = NULL;
+} // cBufferRecorder::Action
+
+void cBufferRecorder::Activate(bool On) {
+	if(!On && liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0);
+	cRecorder::Activate(On);
+} // cBufferRecorder::Activate
+
+void cBufferRecorder::Receive(uchar *Data, int Length) {
+	if(!dropData) cRecorder::Receive(Data, Length);
+} // cBufferRecorder::Receive
+
+void cBufferRecorder::FillInitialData(uchar *Data, int Size) {
+	if(liveBufferIndex) {
+		int64_t search_pts = Data ? TsGetPts(Data, Size) : -1;
+		int maxWait = WAIT_WRITING_COUNT;
+		uchar buffer[MAXFRAMESIZE];
+		int Length;
+		bool Independent;
+		bool found = false;
+		while(!Data || (Size >= TS_SIZE)) {
+			cUnbufferedFile *file = ((cLiveIndex *)liveBufferIndex)->GetNextBuffer(Length, Independent);
+			if(!file) {
+				if(((cLiveIndex *)liveBufferIndex)->WritingBufferCanceled()) {
+					isyslog("Writing buffer canceled by user");
+					if(fileSize) TsSetTeiOnBrokenPackets(Data, Size);
+					((cLiveIndex *)liveBufferIndex)->SetBufferStart(0);
+					liveBufferIndex = NULL;
+					return;
+				} // if
+				if(!Data || !Size) return;
+				if(!maxWait--)
+					break;
+				usleep(WAIT_WRITING_SLEEP);
+				continue;
+			} // if
+			if (!NextFile())
+				break;
+			int len = ReadFrame(file, buffer, Length, sizeof(buffer));
+			if(len < TS_SIZE) {
+				isyslog("Failed to read live buffer data");
+				break;
+			} // if
+			if(Data && Independent && (search_pts == TsGetPts(buffer, len))) {
+				found = true;
+				break;
+			} // if
+			if (index)
+				index->Write(Independent, fileName->Number(), fileSize);
+			if (recordFile->Write(buffer, len) < 0) {
+				isyslog("Failed to write live buffer data");
+				break;
+			} // if
+			fileSize += len;
+		} // while
+		if(Data) {
+			isyslog("%lld bytes from live buffer %swritten to recording", fileSize, found ? "seamless ": "");
+			if(!found && fileSize) TsSetTeiOnBrokenPackets(Data, Size);
+			((cLiveIndex *)liveBufferIndex)->SetBufferStart(0);
+			liveBufferIndex = NULL;
+		} else if(((cLiveIndex *)liveBufferIndex)->WritingBufferCanceled()) {
+			isyslog("%lld bytes from live buffer written to recording (aborted)", fileSize);
+			((cLiveIndex *)liveBufferIndex)->SetBufferStart(0);
+			liveBufferIndex = NULL;
+		} // if
+	} else if (Data && fileSize)
+		TsSetTeiOnBrokenPackets(Data, Size);
+} // cBufferRecorder::FillInitialData
+
+#endif /*VDRVERSNUM*/
+#endif /*USE_LIVEBUFFER*/
diff --git a/livebuffer.h b/livebuffer.h
new file mode 100644
index 0000000..8382d7d
--- /dev/null
+++ b/livebuffer.h
@@ -0,0 +1,47 @@
+#ifndef LIVEBUFFER_H
+#define LIVEBUFFER_H
+
+#ifdef USE_LIVEBUFFER
+#include "config.h"
+#if VDRVERSNUM >= 10716
+
+#include "recorder.h"
+
+class cLiveRecorder : public cRecorder {
+public:
+	cLiveRecorder(const cChannel *Channel);
+	virtual bool NextFile(void);
+	virtual ~cLiveRecorder();
+	virtual bool IsWritingBuffer();
+	virtual void CancelWritingBuffer();
+	virtual int LastIFrame();
+	virtual int LastFrame();
+	virtual void SetResume(int Index);
+	virtual bool SetBufferStart(time_t Start);
+	virtual cIndex *GetIndex();
+	static bool Cleanup();
+	static bool Prepare();
+	static const char *FileName();
+protected:
+	virtual void Activate(bool On);
+	virtual void Receive(uchar *Data, int Length);
+	bool broken;
+	static cString liveFileName;
+}; // cLiveRecorder
+
+class cBufferRecorder : public cRecorder {
+public:
+	cBufferRecorder(const char *FileName, const cChannel *Channel, int Priority, cIndex *LiveBufferIndex);
+	virtual ~cBufferRecorder();
+	virtual void FillInitialData(uchar *Data, int Size);
+protected:
+	virtual void Action(void);
+	virtual void Activate(bool On);
+	virtual void Receive(uchar *Data, int Length);
+	cIndex *liveBufferIndex;
+	bool dropData;
+}; // cBufferRecorder
+
+#endif /*VDRVERSNUM*/
+#endif /*USE_LIVEBUFFER*/
+#endif /*LIVEBUFFER_H*/
diff --git a/menu.c b/menu.c
index ef2bb46..621d2ce 100644
--- a/menu.c
+++ b/menu.c
@@ -3043,7 +3043,11 @@ eOSState cMenuSetupCAM::ProcessKey(eKeys Key)
 
 class cMenuSetupRecord : public cMenuSetupBase {
 private:
-  const char *pauseKeyHandlingTexts[3];
+#ifdef USE_LIVEBUFFER
+  const char *pauseKeyHandlingTexts[4];
+#else
+   const char *pauseKeyHandlingTexts[3];
+#endif /*USE_LIVEBUFFER*/
   const char *delTimeshiftRecTexts[3];
 public:
   cMenuSetupRecord(void);
@@ -3054,6 +3058,9 @@ cMenuSetupRecord::cMenuSetupRecord(void)
   pauseKeyHandlingTexts[0] = tr("do not pause live video");
   pauseKeyHandlingTexts[1] = tr("confirm pause live video");
   pauseKeyHandlingTexts[2] = tr("pause live video");
+#ifdef USE_LIVEBUFFER
+  pauseKeyHandlingTexts[3] = tr("Timeshift");
+#endif /*USE_LIVEBUFFER*/ 
   delTimeshiftRecTexts[0] = tr("no");
   delTimeshiftRecTexts[1] = tr("confirm");
   delTimeshiftRecTexts[2] = tr("yes");
@@ -3063,7 +3070,12 @@ cMenuSetupRecord::cMenuSetupRecord(void)
   Add(new cMenuEditIntItem( tr("Setup.Recording$Primary limit"),             &data.PrimaryLimit, 0, MAXPRIORITY));
   Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"),          &data.DefaultPriority, 0, MAXPRIORITY));
   Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"),      &data.DefaultLifetime, 0, MAXLIFETIME));
-  Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"),        &data.PauseKeyHandling, 3, pauseKeyHandlingTexts));
+#ifdef USE_LIVEBUFFER
+  Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"),        &data.PauseKeyHandling, 4, pauseKeyHandlingTexts));
+  Add(new cMenuEditIntItem( tr("Timeshift size (min)"),                     &data.LiveBufferSize, 1, 300)); // TODO fix name and min/max values
+#else
+   Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"),        &data.PauseKeyHandling, 3, pauseKeyHandlingTexts));
+#endif /*USE_LIVEBUFFER*/
   Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"),            &data.PausePriority, 0, MAXPRIORITY));
   Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"),        &data.PauseLifetime, 0, MAXLIFETIME));
   Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"),          &data.UseSubtitle));
@@ -4134,7 +4146,11 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause)
   isyslog("record %s", fileName);
   if (MakeDirs(fileName, true)) {
      const cChannel *ch = timer->Channel();
+#ifdef USE_LIVEBUFFER
+     recorder = new cBufferRecorder(fileName, ch, timer->Priority(), cRecordControls::GetLiveBuffer(timer));
+#else
      recorder = new cRecorder(fileName, ch, timer->Priority());
+#endif
      if (device->AttachReceiver(recorder)) {
         Recording.WriteInfo();
         cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
@@ -4219,6 +4235,10 @@ bool cRecordControl::Process(time_t t)
 cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL };
 int cRecordControls::state = 0;
 
+#ifdef USE_LIVEBUFFER
+cLiveRecorder *cRecordControls::liveRecorder = NULL;
+#endif /*USE_LIVEBUFFER*/
+
 bool cRecordControls::Start(cTimer *Timer, bool Pause)
 {
   static time_t LastNoDiskSpaceMessage = 0;
@@ -4290,8 +4310,31 @@ void cRecordControls::Stop(const char *InstantId)
       }
 }
 
+
+#ifdef USE_LIVEBUFFER
+bool cRecordControls::StartLiveBuffer(eKeys Key) {
+   if(Setup.PauseKeyHandling == 3 && liveRecorder) {
+     int pos = liveRecorder->LastIFrame();
+     isyslog("Enter timeshift at %d / %d", pos, liveRecorder->LastFrame());
+     liveRecorder->SetResume(pos?pos:liveRecorder->LastFrame());
+     cReplayControl::SetRecording(cLiveRecorder::FileName(), tr("Timeshift mode"));
+     cReplayControl *rc = new cReplayControl;
+     cControl::Launch(rc);
+     cControl::Attach();
+     rc->ProcessKey(Key);
+     rc->Show(); // show progressbar at the start of livebuffer
+     return true;
+  } // if
+  return false;
+} // cRecordControls::StartLiveBuffer
+#endif /*USE_LIVEBUFFER*/
+
 bool cRecordControls::PauseLiveVideo(void)
 {
+#ifdef USE_LIVEBUFFER
+  if(StartLiveBuffer(kPause))
+     return true;
+#endif /*USE_LIVEBUFFER*/
   Skins.Message(mtStatus, tr("Pausing live video..."));
   cReplayControl::SetRecording(NULL, NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed()
   if (Start(NULL, true)) {
@@ -4308,6 +4351,54 @@ bool cRecordControls::PauseLiveVideo(void)
   return false;
 }
 
+#ifdef USE_LIVEBUFFER
+void cRecordControls::SetLiveChannel(cDevice *Device, const cChannel *Channel) {
+	if(liveRecorder) {
+		if(Channel && Device && (liveRecorder->ChannelID()==Channel->GetChannelID()))
+			Device->AttachReceiver(liveRecorder);
+		else
+			DELETENULL(liveRecorder);
+	} // if
+	if(Device && Channel) cControl::Launch(new cTransferControl(Device, Channel));
+	if(Setup.PauseKeyHandling == 3 && Channel && Device && !liveRecorder) { 
+		if (cLiveRecorder::Prepare()) {
+			liveRecorder = new cLiveRecorder(Channel);
+			if(!Device->AttachReceiver(liveRecorder))
+				DELETENULL(liveRecorder);
+		} // if
+	} // if
+} // cRecordControls::SetLiveChannel
+
+bool cRecordControls::CanSetLiveChannel(const cChannel *Channel) {
+	if(liveRecorder && Channel && (liveRecorder->ChannelID()==Channel->GetChannelID())) return true;
+	return !IsWritingBuffer();
+} // cRecordControls::CanSetLiveChannel
+
+bool cRecordControls::IsWritingBuffer() {
+	return liveRecorder ? liveRecorder->IsWritingBuffer() : false;
+} // cRecordControls::IsWritingBuffer
+
+void cRecordControls::CancelWritingBuffer() {
+	if(liveRecorder && liveRecorder->IsWritingBuffer()) {
+		liveRecorder->CancelWritingBuffer();
+		sleep(1); // allow recorder to really stop
+	} // if
+} // cRecordControls::CancelWritingBuffer
+
+cIndex *cRecordControls::GetLiveBuffer(cTimer *Timer) {
+	if(!liveRecorder || !Timer || !Timer->Channel()) return NULL;
+	if(!(liveRecorder->ChannelID() == Timer->Channel()->GetChannelID())) return NULL;
+	if(!liveRecorder->SetBufferStart(Timer->StartTime())) return NULL;
+	return liveRecorder->GetIndex();
+} // cRecordControls::GetLiveBuffer
+
+cIndex *cRecordControls::GetLiveIndex(const char *FileName) {
+	if(!FileName || strcmp(cLiveRecorder::FileName(), FileName)) return NULL;
+	return liveRecorder ? liveRecorder->GetIndex() : NULL;
+} // cRecordControls::GetLiveIndex
+
+#endif /* USE_LIVEBUFFER */
+
 const char *cRecordControls::GetInstantId(const char *LastInstantId)
 {
   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
@@ -4506,21 +4597,30 @@ void cReplayControl::Hide(void)
 
 void cReplayControl::ShowMode(void)
 {
-  if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) {
+  if (visible || (Setup.ShowReplayMode && !cOsd::IsOpen())) {
      bool Play, Forward;
      int Speed;
      if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) {
         bool NormalPlay = (Play && Speed == -1);
+        bool Paused = (!Play && Speed == -1);
 
         if (!visible) {
            if (NormalPlay)
               return; // no need to do indicate ">" unless there was a different mode displayed before
            visible = modeOnly = true;
+
+           // if newly paused show full replay osd; ie modeOnly = false
+           if (Paused)  {
+               modeOnly = (lastPlay == Play);
+           }
+
            displayReplay = Skins.Current()->DisplayReplay(modeOnly);
            }
 
-        if (modeOnly && !timeoutShow && NormalPlay)
+        // osd times out when replaying normally OR when paused and full osd is shown
+        if (!timeoutShow && (NormalPlay|| (!modeOnly && Paused)))
            timeoutShow = time(NULL) + MODETIMEOUT;
+
         displayReplay->SetMode(Play, Forward, Speed);
         lastPlay = Play;
         lastForward = Forward;
@@ -4534,6 +4634,45 @@ bool cReplayControl::ShowProgress(bool Initial)
   int Current, Total;
 
   if (GetIndex(Current, Total) && Total > 0) {
+#ifdef USE_LIVEBUFFER
+     int first=0;
+     cIndex *idx = cRecordControls::GetLiveIndex(fileName);
+     if(idx) first = idx->First(); // Normalize displayed values
+     Current -= first;
+     if(Current < 0) Current = 0;
+     Total   -= first;
+     if(Total < 0) Total = 0;
+     time_t now = time(NULL);
+     static time_t last_sched_check = 0;
+     if(displayReplay && idx && (last_sched_check != now)) {
+        last_sched_check = now; // Only check every second
+        cSchedulesLock SchedulesLock;
+        const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
+        if (Schedules) {
+           const char *display_title = NULL;// = title;
+           const cSchedule *Schedule = Schedules->GetSchedule(Channels.GetByNumber(cDevice::CurrentChannel()));
+           if (Schedule) {
+              time_t Time = now - round(((double)Total - Current) / FramesPerSecond());
+              const cEvent *event = Schedule->GetEventAround(Time);
+              if (event) display_title = event->Title();
+           } // if
+
+           // no event title; show channel name
+           if (!display_title) {
+               cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
+               display_title = channel->Name();
+           }
+
+           // set title as "Timeshift mode: <event title> "
+           // OR "Timeshift mode: <channel name>"
+           // if neither is possible leave title as such
+           if (display_title)
+               displayReplay->SetTitle(cString::sprintf("%s: %s",
+                                                        tr("Timeshift mode"),
+                                                        display_title));
+        } // if
+     } // if
+#endif /*USE_LIVEBUFFER*/
      if (!visible) {
         displayReplay = Skins.Current()->DisplayReplay(modeOnly);
         displayReplay->SetMarks(&marks);
@@ -4635,6 +4774,9 @@ void cReplayControl::TimeSearchProcess(eKeys Key)
 
 void cReplayControl::TimeSearch(void)
 {
+#ifdef USE_LIVEBUFFER
+  if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
   timeSearchTime = timeSearchPos = 0;
   timeSearchHide = false;
   if (modeOnly)
@@ -4653,6 +4795,9 @@ void cReplayControl::TimeSearch(void)
 
 void cReplayControl::MarkToggle(void)
 {
+#ifdef USE_LIVEBUFFER
+  if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
   int Current, Total;
   if (GetIndex(Current, Total, true)) {
      cMark *m = marks.Get(Current);
@@ -4673,6 +4818,9 @@ void cReplayControl::MarkToggle(void)
 
 void cReplayControl::MarkJump(bool Forward)
 {
+#ifdef USE_LIVEBUFFER
+  if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
   if (marks.Count()) {
      int Current, Total;
      if (GetIndex(Current, Total)) {
@@ -4687,6 +4835,9 @@ void cReplayControl::MarkJump(bool Forward)
 
 void cReplayControl::MarkMove(bool Forward)
 {
+#ifdef USE_LIVEBUFFER
+  if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
   int Current, Total;
   if (GetIndex(Current, Total)) {
      cMark *m = marks.Get(Current);
@@ -4711,6 +4862,9 @@ void cReplayControl::MarkMove(bool Forward)
 
 void cReplayControl::EditCut(void)
 {
+#ifdef USE_LIVEBUFFER
+  if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
   if (fileName) {
      Hide();
      if (!cCutter::Active()) {
@@ -4729,6 +4883,9 @@ void cReplayControl::EditCut(void)
 
 void cReplayControl::EditTest(void)
 {
+#ifdef USE_LIVEBUFFER
+  if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
   int Current, Total;
   if (GetIndex(Current, Total)) {
      cMark *m = marks.Get(Current);
@@ -4760,7 +4917,14 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
   if (Key == kNone)
      marks.Update();
   if (visible) {
+
+      if (Key != kNone /*&& !modeOnly*/ && timeoutShow) {
+          printf("timeout reset +%d\n", MODETIMEOUT);
+          timeoutShow = time(NULL) + MODETIMEOUT;
+      }
+
      if (timeoutShow && time(NULL) > timeoutShow) {
+         printf("timed out \n");
         Hide();
         ShowMode();
         timeoutShow = 0;
@@ -4777,12 +4941,34 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
      return osContinue;
      }
   bool DoShowMode = true;
+
+#ifdef USE_LIVEBUFFER
+  if (cRecordControls::GetLiveIndex(fileName) && (Key >= k0) && (Key <= k9))
+      return osSwitchChannel;
+#endif /*USE_LIVEBUFFER*/
   switch (int(Key)) {
     // Positioning:
+#ifdef USE_LIVEBUFFER
+    case kUp:      if(cRecordControls::GetLiveIndex(fileName)) {
+                      cDevice::SwitchChannel(1);
+                      return osEnd;
+                   } // if
+                   // NO break
+  case kPlay:
+      Play(); break;
+    case kDown:    if(cRecordControls::GetLiveIndex(fileName)) {
+                      cDevice::SwitchChannel(-1);
+                      return osEnd;
+                   } // if
+                   // NO break
+  case kPause:   Pause();
+      break;
+#else
     case kPlay:
     case kUp:      Play(); break;
     case kPause:
     case kDown:    Pause(); break;
+#endif /*USE_LIVEBUFFER*/
     case kFastRew|k_Release:
     case kLeft|k_Release:
                    if (Setup.MultiSpeedMode) break;
@@ -4793,15 +4979,52 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
                    if (Setup.MultiSpeedMode) break;
     case kFastFwd:
     case kRight:   Forward(); break;
-    case kRed:     TimeSearch(); break;
+    //case kRed:     TimeSearch(); break;
     case kGreen|k_Repeat:
     case kGreen:   SkipSeconds(-60); break;
     case kYellow|k_Repeat:
     case kYellow:  SkipSeconds( 60); break;
+#ifdef USE_LIVEBUFFER
+    case kRed:     if(cRecordControls::GetLiveIndex(fileName)) {
+
+                       if (!(visible && !modeOnly)) return osUnknown;
+                       else {} // fall through to case kRecord
+                               // since Timeshift ON and replay OSD is shown
+                       } // if
+                       else { //timeshift off
+                           TimeSearch();
+                           break;
+                       } // else
+                       // No break
+    case kRecord:  if(cRecordControls::GetLiveIndex(fileName)) {
+                      int frames = 0;
+                      int Current, Total;
+                      if(GetIndex(Current, Total))
+                         frames = Total-Current;
+                      cTimer *timer = new cTimer(true, false, Channels.GetByNumber(cDevice::CurrentChannel()), frames / FramesPerSecond());
+                      Timers.Add(timer);
+                      Timers.SetModified();
+                      if (cRecordControls::Start(timer))
+                         Skins.Message(mtInfo, tr("Recording started"));
+                      else
+                         Timers.Del(timer);
+                   } // if
+                   break;
+    case kBlue:    if(cRecordControls::GetLiveIndex(fileName))
+                       if(!(visible && !modeOnly))
+                           return osUnknown;
+                   //NO break
+    case kStop:    Hide();
+                   Stop();
+                   return osEnd;
+#else
+    case kRed:     TimeSearch(); break;
     case kStop:
     case kBlue:    Hide();
                    Stop();
                    return osEnd;
+#endif /*USE_LIVEBUFFER*/
+
     default: {
       DoShowMode = false;
       switch (int(Key)) {
@@ -4832,7 +5055,20 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
                            else
                               Show();
                            break;
-            case kBack:    if (Setup.DelTimeshiftRec) { 
+            case kBack:
+#ifdef USE_LIVEBUFFER
+                           if (visible && !modeOnly) {
+                              Hide();
+                              DoShowMode = true;
+                              break;
+                           }
+                           if(cRecordControls::GetLiveIndex(fileName)) {
+                              Hide();
+                              Stop();
+                              return osEnd;
+                           } // if
+#endif /*USE_LIVEBUFFER*/
+                           if (Setup.DelTimeshiftRec) {
                               cRecordControl* rc = cRecordControls::GetRecordControl(fileName);
                               return rc && rc->InstantId() ? osEnd : osRecordings;
                               }
diff --git a/menu.h b/menu.h
index ec1c175..2d55470 100644
--- a/menu.h
+++ b/menu.h
@@ -18,6 +18,12 @@
 #include "menuitems.h"
 #include "recorder.h"
 #include "skins.h"
+#ifdef USE_LIVEBUFFER
+#include "livebuffer.h"
+#endif /*USE_LIVEBUFFER*/
+
+
+
 
 class cMenuText : public cOsdMenu {
 private:
@@ -236,10 +242,18 @@ class cRecordControls {
 private:
   static cRecordControl *RecordControls[];
   static int state;
+#ifdef USE_LIVEBUFFER
+protected:
+  friend class cRecordControl;
+  static cLiveRecorder *liveRecorder;
+#endif /*USE_LIVEBUFFER*/
 public:
   static bool Start(cTimer *Timer = NULL, bool Pause = false);
   static void Stop(const char *InstantId);
   static bool PauseLiveVideo(void);
+#ifdef USE_LIVEBUFFER
+  static bool StartLiveBuffer(eKeys Key);
+#endif /*USE_LIVEBUFFER*/
   static const char *GetInstantId(const char *LastInstantId);
   static cRecordControl *GetRecordControl(const char *FileName);
   static void Process(time_t t);
@@ -248,6 +262,14 @@ public:
   static void Shutdown(void);
   static void ChangeState(void) { state++; }
   static bool StateChanged(int &State);
+#ifdef USE_LIVEBUFFER
+  static void SetLiveChannel(cDevice *Device, const cChannel *Channel);
+  static bool CanSetLiveChannel(const cChannel *Channel);
+  static bool IsWritingBuffer();
+  static void CancelWritingBuffer();
+  static cIndex *GetLiveBuffer(cTimer *Timer);
+  static cIndex *GetLiveIndex(const char *FileName);
+#endif /*USE_LIVEBUFFER*/
   };
 
 class cReplayControl : public cDvbPlayerControl {
diff --git a/osdbase.h b/osdbase.h
index 91c5ff7..27c22b7 100644
--- a/osdbase.h
+++ b/osdbase.h
@@ -33,6 +33,9 @@ enum eOSState { osUnknown,
                 osSwitchDvb,
                 osBack,
                 osEnd,
+#ifdef USE_LIVEBUFFER
+                osSwitchChannel,
+#endif /*USE_LIVEBUFFER*/
                 os_User, // the following values can be used locally
                 osUser1,
                 osUser2,
diff --git a/player.c b/player.c
index 3490565..9aa2956 100644
--- a/player.c
+++ b/player.c
@@ -10,6 +10,11 @@
 #include "player.h"
 #include "i18n.h"
 
+#ifdef USE_LIVEBUFFER
+#include "menu.h"
+#include "transfer.h"
+#endif /*USE_LIVEBUFFER*/
+
 // --- cPlayer ---------------------------------------------------------------
 
 cPlayer::cPlayer(ePlayMode PlayMode)
@@ -68,6 +73,12 @@ cControl *cControl::Control(void)
 
 void cControl::Launch(cControl *Control)
 {
+#ifdef USE_LIVEBUFFER
+  if(!dynamic_cast<cTransferControl *>(Control)) {
+     if(!dynamic_cast<cReplayControl *>(Control) || strcmp(cLiveRecorder::FileName(), cReplayControl::NowReplaying()))
+        cRecordControls::SetLiveChannel(NULL, NULL);
+  } // if
+#endif /*USE_LIVEBUFFER*/
   cMutexLock MutexLock(&mutex);
   cControl *c = control; // keeps control from pointing to uninitialized memory
   control = Control;
diff --git a/po/de_DE.po b/po/de_DE.po
index 6d5b822..355348b 100644
--- a/po/de_DE.po
+++ b/po/de_DE.po
@@ -25,6 +25,9 @@ msgstr "Kanal nicht verf
 msgid "Can't start Transfer Mode!"
 msgstr "Transfer-Mode kann nicht gestartet werden!"
 
+msgid "Still writing timeshift data to recording. Abort?"
+msgstr "Timeshift-Daten werden noch in Aufnahme kopiert. Abbrechen?"
+
 msgid "off"
 msgstr "aus"
 
diff --git a/recorder.c b/recorder.c
index a6cab47..70e4659 100644
--- a/recorder.c
+++ b/recorder.c
@@ -24,6 +24,9 @@
 cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
 :cReceiver(Channel, Priority)
 ,cThread("recording")
+#ifdef USE_LIVEBUFFER
+,handleError(true)
+#endif /*USE_LIVEBUFFER*/
 {
   recordingName = strdup(FileName);
 
@@ -140,6 +143,9 @@ void cRecorder::Action(void)
                     InfoWritten = true;
                     }
                  if (FirstIframeSeen || frameDetector->IndependentFrame()) {
+#ifdef USE_LIVEBUFFER
+                    if(!FirstIframeSeen) FillInitialData(b, r);
+#endif /*USE_LIVEBUFFER*/
                     FirstIframeSeen = true; // start recording with the first I-frame
                     if (!NextFile())
                        break;
@@ -165,7 +171,11 @@ void cRecorder::Action(void)
               ringBuffer->Del(Count);
               }
            }
+#ifdef USE_LIVEBUFFER
+        if (handleError && (time(NULL) - t > MAXBROKENTIMEOUT)) {
+#else
         if (time(NULL) - t > MAXBROKENTIMEOUT) {
+#endif
            esyslog("ERROR: video data stream broken");
            ShutdownHandler.RequestEmergencyExit();
            t = time(NULL);
diff --git a/recorder.h b/recorder.h
index 05cc42b..1d2aa04 100644
--- a/recorder.h
+++ b/recorder.h
@@ -17,18 +17,33 @@
 #include "thread.h"
 
 class cRecorder : public cReceiver, cThread {
+#ifdef USE_LIVEBUFFER
+protected:
+#else
 private:
+#endif /*USE_LIVEBUFFER*/
   cRingBufferLinear *ringBuffer;
   cFrameDetector *frameDetector;
   cPatPmtGenerator patPmtGenerator;
   cFileName *fileName;
+#ifdef USE_LIVEBUFFER
+  cIndex *index;
+  bool handleError;
+#else
   cIndexFile *index;
+#endif /*USE_LIVEBUFFER*/
   cUnbufferedFile *recordFile;
   char *recordingName;
   off_t fileSize;
   time_t lastDiskSpaceCheck;
+#ifdef USE_LIVEBUFFER
+  virtual bool RunningLowOnDiskSpace(void);
+  virtual bool NextFile(void);
+  virtual void FillInitialData(uchar *Data, int Size) {};
+#else
   bool RunningLowOnDiskSpace(void);
   bool NextFile(void);
+#endif /*USE_LIVEBUFFER*/
 protected:
   virtual void Activate(bool On);
   virtual void Receive(uchar *Data, int Length);
diff --git a/recording.h b/recording.h
index 37979ec..52d57c8 100644
--- a/recording.h
+++ b/recording.h
@@ -264,7 +264,26 @@ public:
 struct tIndexTs;
 class cIndexFileGenerator;
 
+#ifdef USE_LIVEBUFFER
+class cIndex {
+public:
+  virtual bool Ok(void) =0;
+  virtual bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset) =0;
+  virtual bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL) =0;
+  virtual int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false) =0;
+  virtual int Get(uint16_t FileNumber, off_t FileOffset) =0;
+  virtual int First(void) {return 0;};
+  virtual int Last(void) =0;
+  virtual int GetResume(void) =0;
+  virtual bool StoreResume(int Index) =0;
+  virtual bool IsStillRecording(void) =0;
+  virtual void Delete(void) =0;
+  };
+
+class cIndexFile : public cIndex {
+#else
 class cIndexFile {
+#endif /*USE_LIVEBUFFER*/
 private:
   int f;
   cString fileName;
diff --git a/timers.c b/timers.c
index 54ed1ba..c2298fa 100644
--- a/timers.c
+++ b/timers.c
@@ -25,7 +25,11 @@
 
 // --- cTimer ----------------------------------------------------------------
 
+#ifdef USE_LIVEBUFFER
+cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel, int Forerun)
+#else
 cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
+#endif /*USE_LIVEBUFFER*/
 {
   startTime = stopTime = 0;
   lastSetEvent = 0;
@@ -35,7 +39,11 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
   if (Instant)
      SetFlags(tfActive | tfInstant);
   channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
+#ifdef USE_LIVEBUFFER
+  time_t t = time(NULL) - Forerun;
+#else
   time_t t = time(NULL);
+#endif /*USE_LIVEBUFFER*/
   struct tm tm_r;
   struct tm *now = localtime_r(&t, &tm_r);
   day = SetTime(t, 0);
diff --git a/timers.h b/timers.h
index 1d733ee..d35e042 100644
--- a/timers.h
+++ b/timers.h
@@ -43,7 +43,11 @@ private:
   char *aux;
   const cEvent *event;
 public:
+#ifdef USE_LIVEBUFFER
+  cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL, int Forerun = 0);
+#else
   cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL);
+#endif /*USE_LIVEBUFFER*/
   cTimer(const cEvent *Event);
   cTimer(const cTimer &Timer);
   virtual ~cTimer();
diff --git a/vdr.c b/vdr.c
index c32e45f..b9c4460 100644
--- a/vdr.c
+++ b/vdr.c
@@ -218,6 +218,9 @@ int main(int argc, char *argv[])
 
   static struct option long_options[] = {
       { "audio",    required_argument, NULL, 'a' },
+#ifdef USE_LIVEBUFFER
+      { "buffer",   required_argument, NULL, 'b' },
+#endif /* USE_LIVEBUFFER */
       { "config",   required_argument, NULL, 'c' },
       { "daemon",   no_argument,       NULL, 'd' },
       { "device",   required_argument, NULL, 'D' },
@@ -251,10 +254,20 @@ int main(int argc, char *argv[])
     };
 
   int c;
+#ifdef USE_LIVEBUFFER
+  while ((c = getopt_long(argc, argv, "a:b:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) {
+#else
   while ((c = getopt_long(argc, argv, "a:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) {
+#endif /* USE_LIVEBUFFER */
         switch (c) {
           case 'a': AudioCommand = optarg;
                     break;
+#ifdef USE_LIVEBUFFER
+          case 'b': BufferDirectory = optarg;
+                    if(optarg && *optarg && optarg[strlen(optarg)-1] == '/')
+                       optarg[strlen(optarg)-1] = 0;
+                    break;
+#endif /* USE_LIVEBUFFER */
           case 'c': ConfigDirectory = optarg;
                     break;
           case 'd': DaemonMode = true; break;
@@ -420,6 +433,9 @@ int main(int argc, char *argv[])
      if (DisplayHelp) {
         printf("Usage: vdr [OPTIONS]\n\n"          // for easier orientation, this is column 80|
                "  -a CMD,   --audio=CMD    send Dolby Digital audio to stdin of command CMD\n"
+#ifdef USE_LIVEBUFFER
+               "  -b DIR,   --buffer=DIR   use DIR as LiveBuffer directory\n"
+#endif /*USE_LIVEBUFFER*/
                "  -c DIR,   --config=DIR   read config files from DIR (default: %s)\n"
                "  -d,       --daemon       run in daemon mode\n"
                "  -D NUM,   --device=NUM   use only the given DVB device (NUM = 0, 1, 2...)\n"
@@ -586,9 +602,12 @@ int main(int argc, char *argv[])
 
   if (!PluginManager.LoadPlugins(true))
      EXIT(2);
-
   // Configuration data:
 
+#ifdef USE_LIVEBUFFER
+  if (!BufferDirectory)
+     BufferDirectory = VideoDirectory;
+#endif /*USE_LIVEBUFFER*/
   if (!ConfigDirectory)
      ConfigDirectory = DEFAULTCONFDIR;
 
@@ -1092,6 +1111,15 @@ int main(int argc, char *argv[])
                   cDisplaySubtitleTracks::Process(key);
                key = kNone;
                break;
+#ifdef USE_LIVEBUFFER
+          case kFastRew:
+               if (!Interact) {
+                  DELETE_MENU;
+                  if(cRecordControls::StartLiveBuffer(key))
+                     key = kNone;
+               } // if
+               break;
+#endif /*USE_LIVEBUFFER*/
           // Pausing live video:
           case kPause:
                if (!cControl::Control()) {
@@ -1199,6 +1227,28 @@ int main(int argc, char *argv[])
                             else
                                cControl::Shutdown();
                             break;
+#ifdef USE_LIVEBUFFER
+             case osSwitchChannel:
+                            switch (key) {
+                                // Toggle channels:
+                                case kChanPrev:
+                                case k0: {
+                                    if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel
+                                        || (LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1]))
+                                        PreviousChannelIndex ^= 1;
+                                    Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
+                                    break;
+                                }
+                                case k1 ... k9:
+                                    DELETE_MENU;
+                                    cControl::Shutdown();
+                                    Menu = new cDisplayChannel(NORMALKEY(key));
+                                    break;
+                                default:
+                                    break;
+                            } // switch
+                            break;
+#endif /*USE_LIVEBUFFER*/
              default:       ;
              }
            }
diff --git a/videodir.c b/videodir.c
index 7331a85..14d4373 100644
--- a/videodir.c
+++ b/videodir.c
@@ -20,6 +20,9 @@
 #include "tools.h"
 
 const char *VideoDirectory = VIDEODIR;
+#ifdef USE_LIVEBUFFER
+const char *BufferDirectory = NULL;
+#endif /*USE_LIVEBUFFER*/
 
 class cVideoDirectory {
 private:
@@ -106,17 +109,32 @@ const char *cVideoDirectory::Adjust(const char *FileName)
 cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags)
 {
   const char *ActualFileName = FileName;
+#ifdef USE_LIVEBUFFER
+  bool SepBufferDir = false;
 
   // Incoming name must be in base video directory:
+   if (strstr(FileName, VideoDirectory) != FileName) {
+     if (strstr(FileName, BufferDirectory) == FileName)
+        SepBufferDir = true;
+     else {
+#else
   if (strstr(FileName, VideoDirectory) != FileName) {
+#endif /*USE_LIVEBUFFER*/
      esyslog("ERROR: %s not in %s", FileName, VideoDirectory);
      errno = ENOENT; // must set 'errno' - any ideas for a better value?
      return NULL;
      }
+#ifdef USE_LIVEBUFFER
+     }
+#endif /*USE_LIVEBUFFER*/
   // Are we going to create a new file?
   if ((Flags & O_CREAT) != 0) {
      cVideoDirectory Dir;
+#ifdef USE_LIVEBUFFER
+     if (Dir.IsDistributed() && !SepBufferDir) {
+#else
      if (Dir.IsDistributed()) {
+#endif /*USE_LIVEBUFFER*/
         // Find the directory with the most free space:
         int MaxFree = Dir.FreeMB();
         while (Dir.Next()) {
diff --git a/videodir.h b/videodir.h
index 5e9aef5..57b8652 100644
--- a/videodir.h
+++ b/videodir.h
@@ -14,6 +14,9 @@
 #include "tools.h"
 
 extern const char *VideoDirectory;
+#ifdef USE_LIVEBUFFER
+extern const char *BufferDirectory;
+#endif /*USE_LIVEBUFFER*/
 
 cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags);
 int CloseVideoFile(cUnbufferedFile *File);
-- 
1.7.5.4

--- vdr-1.7.21-r1.ebuild	2011-10-25 03:10:08.000000000 +0200
+++ vdr-1.7.21-r2.ebuild	2011-12-23 13:10:44.000000000 +0100
@@ -7,7 +7,7 @@
 inherit eutils flag-o-matic multilib
 
 # Switches supported by extensions-patch
-EXT_PATCH_FLAGS="alternatechannel cutterlimit
+EXT_PATCH_FLAGS="alternatechannel cutterlimit livebuffer
 	ddepgentry dvlvidprefer graphtft hardlinkcutter jumpplay
 	lnbshare liemikuutio mainmenuhooks menuorg noepg pinplugin setup timerinfo
 	yaepg lircsettings"
@@ -295,27 +295,8 @@
 
 	fi
 
-	# apply local patches defined by variable VDR_LOCAL_PATCHES_DIR
-	if test -n "${VDR_LOCAL_PATCHES_DIR}"; then
-		local dir_tmp_var
-		local LOCALPATCHES_SUBDIR=${PV}
-		for dir_tmp_var in allversions-fallback ${PV%_p*} ${PV} ; do
-			if [[ -d ${VDR_LOCAL_PATCHES_DIR}/${dir_tmp_var} ]]; then
-				LOCALPATCHES_SUBDIR="${dir_tmp_var}"
-			fi
-		done
-
-		echo
-		if [[ ${LOCALPATCHES_SUBDIR} == ${PV} ]]; then
-			einfo "Applying local patches"
-		else
-			einfo "Applying local patches (Using subdirectory: ${LOCALPATCHES_SUBDIR})"
-		fi
-
-		for LOCALPATCH in ${VDR_LOCAL_PATCHES_DIR}/${LOCALPATCHES_SUBDIR}/*.{diff,patch}; do
-			test -f "${LOCALPATCH}" && epatch "${LOCALPATCH}"
-		done
-	fi
+	# apply local patches
+	epatch_user
 
 	if [[ -n "${VDRSOURCE_DIR}" ]]; then
 		cp -r "${S}" "${T}"/source-tree
_______________________________________________
vdr mailing list
vdr@xxxxxxxxxxx
http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr

[Linux Media]     [Asterisk]     [Photo]     [DCCP]     [Netdev]     [Xorg]     [Util Linux NG]     [Xfree86]     [Devices]     [Big List of Linux Books]     [Fedora Users]     [Webcams]     [Fedora Women]     [ALSA Devel]     [Linux USB]

Powered by Linux