On 11-12-20 05:08 PM, René wrote:
Hi All,
I'm stuck to vdr 1.6.0-2 because i can't find a current LiveBuffer
patch for any current vdr 1.7.x. Does anyone know if there is any work
going on with this great patch?
Regards,
René
_______________________________________________
vdr mailing list
vdr@xxxxxxxxxxx
http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr
Here you go. I grabbed this from the yaVDR source.
Norm
#! /bin/sh /usr/share/dpatch/dpatch-run
## opt-96-livebuffer10-rmm.dpatch by <Gerald Dachs <gda@xxxxxxxxxxx>>
##
## All lines beginning with `## DP:' are a description of the patch.
## DP: No description.
@DPATCH@
diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/config.c vdr-liveb/config.c
--- vdr/config.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/config.c 2011-10-27 09:56:48.312237585 +0200
@@ -419,6 +419,10 @@
VerboseLNBlog = 0;
for (int i = 0; i < MAXDEVICES; i++) CardUsesLnbNr[i] = i + 1;
//ML-Ende
+#ifdef USE_LIVEBUFFER
+ LiveBufferSize = 15;
+ LiveBufferMaxFileSize = 100;
+#endif /*USE_LIVEBUFFER*/
}
cSetup::~cSetup()
@@ -635,6 +639,10 @@
else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(Value);
else if (!strcasecmp(Name, "VolumeSteps")) VolumeSteps = atoi(Value);
else if (!strcasecmp(Name, "VolumeLinearize")) VolumeLinearize = 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
@@ -765,6 +773,9 @@
Store("VolumeLinearize", VolumeLinearize);
Store("ChannelsWrap", ChannelsWrap);
Store("EmergencyExit", EmergencyExit);
+#ifdef USE_LIVEBUFFER
+ Store("LiveBufferSize", LiveBufferSize);
+#endif /* LIVEBUFFER */
//ML
Store("VerboseLNBlog", VerboseLNBlog);
diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/config.h vdr-liveb/config.h
--- vdr/config.h 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/config.h 2011-10-27 09:56:48.312237585 +0200
@@ -315,6 +315,10 @@
int InitialVolume;
int ChannelsWrap;
int EmergencyExit;
+#ifdef USE_LIVEBUFFER
+ int LiveBufferSize;
+ int LiveBufferMaxFileSize;
+#endif /*USE_LIVEBUFFER*/
//ML
#define LNB_SHARING_VERSION "0.1.4"
diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/device.c vdr-liveb/device.c
--- vdr/device.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/device.c 2011-10-27 10:02:55.742237597 +0200
@@ -20,6 +20,10 @@
#include "status.h"
#include "transfer.h"
#include "vdrttxtsubshooks.h"
+#ifdef USE_LIVEBUFFER
+#include "menu.h"
+#include "interface.h"
+#endif /*USE_LIVEBUFFER*/
// --- cLiveSubtitle ---------------------------------------------------------
@@ -758,6 +762,14 @@
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");
}
@@ -893,8 +905,17 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/device.h vdr-liveb/device.h
--- vdr/device.h 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/device.h 2011-10-27 09:56:48.322237585 +0200
@@ -33,7 +33,11 @@
#define MAXVOLUME 255
#define VOLUMEDELTA (MAXVOLUME/Setup.VolumeSteps) // 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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/dvbplayer.c vdr-liveb/dvbplayer.c
--- vdr/dvbplayer.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/dvbplayer.c 2011-10-27 09:56:48.322237585 +0200
@@ -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 @@
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)
@@ -206,7 +212,12 @@
cPtsIndex ptsIndex;
cMarksReload marks;
cFileName *fileName;
+#ifdef USE_LIVEBUFFER
+ cIndex *index;
+ cIndexFile *indexFile;
+#else
cIndexFile *index;
+#endif /*USE_LIVEBUFFER*/
cUnbufferedFile *replayFile;
double framesPerSecond;
bool isPesRecording;
@@ -271,18 +282,35 @@
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()
@@ -290,7 +318,11 @@
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;
}
@@ -394,9 +426,11 @@
bool cutIn = false;
int total = -1;
+#ifndef USE_LIVEBUFFER
readIndex = Resume();
if (readIndex >= 0)
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
+#endif /*USE_LIVEBUFFER*/
if (Setup.PlayJump && readIndex <= 0 && marks.First() && index) {
int Index = marks.First()->position;
@@ -456,6 +490,10 @@
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;
@@ -512,6 +550,15 @@
total = index->Last();
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;
}
@@ -657,7 +704,11 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/livebuffer.c vdr-liveb/livebuffer.c
--- vdr/livebuffer.c 1970-01-01 01:00:00.000000000 +0100
+++ vdr-liveb/livebuffer.c 2011-10-27 09:56:48.322237585 +0200
@@ -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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/livebuffer.h vdr-liveb/livebuffer.h
--- vdr/livebuffer.h 1970-01-01 01:00:00.000000000 +0100
+++ vdr-liveb/livebuffer.h 2011-10-27 09:56:48.322237585 +0200
@@ -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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/Make.config vdr-liveb/Make.config
--- vdr/Make.config 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/Make.config 2011-10-27 09:56:48.212237585 +0200
@@ -29,4 +29,6 @@
BIDI = 1
GRAPHTFT = 1
-DEFINES += -DUSE_GRAPHTFT
+USE_LIVEBUFFER = 1
+DEFINES += -DUSE_GRAPHTFT -DUSE_LIVEBUFFER
+
diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/Makefile vdr-liveb/Makefile
--- vdr/Makefile 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/Makefile 2011-10-27 09:56:48.212237585 +0200
@@ -64,6 +64,11 @@
LIBS += $(shell pkg-config --libs fribidi)
endif
+ifdef USE_LIVEBUFFER
+DEFINES += -DUSE_LIVEBUFFER
+OBJS += livebuffer.o
+endif
+
LIRC_DEVICE ?= /dev/lircd
RCU_DEVICE ?= /dev/ttyS1
diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/menu.c vdr-liveb/menu.c
--- vdr/menu.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/menu.c 2011-10-27 09:56:48.332237585 +0200
@@ -3449,7 +3449,11 @@
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);
@@ -3460,6 +3464,9 @@
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");
@@ -3469,7 +3476,12 @@
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));
@@ -4681,7 +4693,11 @@
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);
@@ -4766,6 +4782,10 @@
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)
{
if (!Timer) {
@@ -4882,8 +4902,31 @@
}
}
+
+#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)) {
@@ -4900,6 +4943,54 @@
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++) {
@@ -5104,21 +5195,30 @@
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;
@@ -5132,6 +5232,45 @@
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);
@@ -5233,6 +5372,9 @@
void cReplayControl::TimeSearch(void)
{
+#ifdef USE_LIVEBUFFER
+ if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
timeSearchTime = timeSearchPos = 0;
timeSearchHide = false;
if (modeOnly)
@@ -5251,6 +5393,9 @@
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);
@@ -5273,6 +5418,9 @@
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)) {
@@ -5296,6 +5444,9 @@
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);
@@ -5320,6 +5471,9 @@
void cReplayControl::EditCut(void)
{
+#ifdef USE_LIVEBUFFER
+ if(cRecordControls::GetLiveIndex(fileName)) return;
+#endif /*USE_LIVEBUFFER*/
if (fileName) {
Hide();
if (!cCutter::Active()) {
@@ -5338,6 +5492,9 @@
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);
@@ -5369,7 +5526,14 @@
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;
@@ -5386,12 +5550,34 @@
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;
@@ -5402,11 +5588,67 @@
if (Setup.MultiSpeedMode) break;
case kFastFwd:
case kRight: Forward(); 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 kPrev|k_Repeat:
+ case kPrev: if (lastSkipTimeout.TimedOut()) {
+ lastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
+ lastSkipKey = kPrev;
+ }
+ else if (RAWKEY(lastSkipKey) != kPrev && lastSkipSeconds > (2 * REPLAYCONTROLSKIPLIMIT)) {
+ lastSkipSeconds /= 2;
+ lastSkipKey = kNone;
+ }
+ lastSkipTimeout.Set(REPLAYCONTROLSKIPTIMEOUT);
+ SkipSeconds(-lastSkipSeconds); break;
+ case kNext|k_Repeat:
+ case kNext: if (lastSkipTimeout.TimedOut()) {
+ lastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
+ lastSkipKey = kNext;
+ }
+ else if (RAWKEY(lastSkipKey) != kNext && lastSkipSeconds > (2 * REPLAYCONTROLSKIPLIMIT)) {
+ lastSkipSeconds /= 2;
+ lastSkipKey = kNone;
+ }
+ lastSkipTimeout.Set(REPLAYCONTROLSKIPTIMEOUT);
+ SkipSeconds(lastSkipSeconds); 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 kPrev|k_Repeat:
case kPrev: if (lastSkipTimeout.TimedOut()) {
lastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
@@ -5433,6 +5675,8 @@
case kBlue: Hide();
Stop();
return osEnd;
+#endif /*USE_LIVEBUFFER*/
+
default: {
bool play, forward;
int speed;
@@ -5494,7 +5738,20 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/menu.h vdr-liveb/menu.h
--- vdr/menu.h 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/menu.h 2011-10-27 09:56:48.332237585 +0200
@@ -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:
@@ -243,10 +249,18 @@
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);
@@ -255,6 +269,14 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/osdbase.h vdr-liveb/osdbase.h
--- vdr/osdbase.h 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/osdbase.h 2011-10-27 09:56:48.332237585 +0200
@@ -33,6 +33,9 @@
osSwitchDvb,
osBack,
osEnd,
+#ifdef USE_LIVEBUFFER
+ osSwitchChannel,
+#endif /*USE_LIVEBUFFER*/
os_User, // the following values can be used locally
osUser1,
osUser2,
diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/player.c vdr-liveb/player.c
--- vdr/player.c 2011-10-17 13:40:24.000000000 +0200
+++ vdr-liveb/player.c 2011-10-27 09:56:48.332237585 +0200
@@ -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 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/po/de_DE.po vdr-liveb/po/de_DE.po
--- vdr/po/de_DE.po 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/po/de_DE.po 2011-10-27 09:56:48.332237585 +0200
@@ -25,6 +25,9 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/recorder.c vdr-liveb/recorder.c
--- vdr/recorder.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/recorder.c 2011-10-27 09:56:48.332237585 +0200
@@ -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 @@
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 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/recorder.h vdr-liveb/recorder.h
--- vdr/recorder.h 2011-10-17 13:40:24.000000000 +0200
+++ vdr-liveb/recorder.h 2011-10-27 09:56:48.342237585 +0200
@@ -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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/recording.h vdr-liveb/recording.h
--- vdr/recording.h 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/recording.h 2011-10-27 09:56:48.342237585 +0200
@@ -288,7 +288,26 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/timers.c vdr-liveb/timers.c
--- vdr/timers.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/timers.c 2011-10-27 09:56:48.342237585 +0200
@@ -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 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/timers.h vdr-liveb/timers.h
--- vdr/timers.h 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/timers.h 2011-10-27 09:56:48.342237585 +0200
@@ -44,7 +44,11 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/vdr.c vdr-liveb/vdr.c
--- vdr/vdr.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/vdr.c 2011-10-27 09:56:48.342237585 +0200
@@ -218,6 +218,9 @@
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' },
@@ -254,10 +257,20 @@
};
int c;
+#ifdef USE_LIVEBUFFER
+ while ((c = getopt_long(argc, argv, "a:b:c:dD:e:E:g:hi:kl: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:kl: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;
@@ -427,6 +440,9 @@
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"
@@ -596,9 +612,12 @@
if (!PluginManager.LoadPlugins(true))
EXIT(2);
-
// Configuration data:
+#ifdef USE_LIVEBUFFER
+ if (!BufferDirectory)
+ BufferDirectory = VideoDirectory;
+#endif /*USE_LIVEBUFFER*/
if (!ConfigDirectory)
ConfigDirectory = DEFAULTCONFDIR;
@@ -1116,6 +1135,15 @@
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()) {
@@ -1223,6 +1251,28 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/videodir.c vdr-liveb/videodir.c
--- vdr/videodir.c 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/videodir.c 2011-10-27 09:56:48.342237585 +0200
@@ -23,6 +23,9 @@
//#define HARDLINK_TEST_ONLY
const char *VideoDirectory = VIDEODIR;
+#ifdef USE_LIVEBUFFER
+const char *BufferDirectory = NULL;
+#endif /*USE_LIVEBUFFER*/
class cVideoDirectory {
private:
@@ -109,17 +112,32 @@
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 -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' vdr/videodir.h vdr-liveb/videodir.h
--- vdr/videodir.h 2011-10-27 09:53:31.000000000 +0200
+++ vdr-liveb/videodir.h 2011-10-27 09:56:48.342237585 +0200
@@ -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);
_______________________________________________
vdr mailing list
vdr@xxxxxxxxxxx
http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr